Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The Hitchhiker's Guide to Ruby GC

The Hitchhiker's Guide to Ruby GC

A brief history of garbage collection in MRI Ruby.

Eric Weinstein

November 16, 2015
Tweet

More Decks by Eric Weinstein

Other Decks in Technology

Transcript

  1. The Hitchhiker’s Guide to Ruby GC # Eric Weinstein #

    RubyConf # 16 November 2015 # San Antonio, TX
  2. Okay, yes, sort of • But not for the reason(s)

    you think! • Database queries (N + 1) • Superlinear time complexity • Ruby being an interpreted language
  3. We’re talking MRI • Not Rubinius or JRuby, which have

    different garbage collectors • I may make comparisons where appropriate, though, so the above could make guest appearances
  4. 1.8.7 • Ruby GC is tracing (as opposed to reference

    counting, e.g. Python) • Mark & sweep • M&S was invented by Alexander Graham Link John McCarthy for LISP in 1959
  5. 1.8.7 (con’t) • Ruby allocates some memory (more on this

    later) • When there’s no more free memory, Ruby marks all active objects, then combines (sweeps) inactive objects into a single list
  6. 1.9.3 • Lazy mark & sweep: sweep in phases to

    reduce the length of each sweep pause • Like 1.8.7, subverts native copy-on-write (this will be on the quiz)
  7. 2.0 • Bitmap marking: we no longer mark objects directly,

    so we’re free to leverage copy- on-write! • This will be important later
  8. 2.1 • Generational GC (two generations: young and old) •

    If you survive 3 collections, you become old • Because most objects die young, you perform (fast) minor GC frequently and (slower) stop- the-world major GC rarely • The RGenGC algorithm (Koichi Sasada, EuRuKo)
  9. 2.2 • Symbol GC: no more symbol DoS • Incremental

    major GC (the RincGC algorithm) • Tricolor marking: white (unmarked), gray (marked, but may refer to white objects), and black (marked, without references to white objects)
  10. Wild White Object Appears! • We create a new (white)

    object, but there are no gray objects with references to it (since there are only black and white objects) • We reclaim our live object by mistake!
  11. Write Barriers • They’re super effective! • We also have

    write barrier protected and write barrier unprotected objects • Pause time is relative to the number of living write barrier unprotected objects • Most objects (String, Array, Hash, or user-defined POROs) are write barrier protected objects, so the pause time for unprotected objects is acceptable
  12. The Memory Model • Ruby objects are 40-byte RValue structures,

    which Ruby allocates into heaps of 16KB each • You get ~400 Ruby objects per heap (16_000 / 40) • Ruby initially allocates ~150 heaps
  13. We Were Making A $#!% Tonne $ bxr console Loading

    development console... > GC.start => nil > GC.stat => {:count=>132, :heap_used=>1456, :heap_length=>2619, :heap_incremen t=>1163, :heap_live_num=>513174, :heap_free_num=>81988, :heap_fina l_num=>0}
  14. Mark && Sweep • Ruby heaps comprise linked lists of

    RValues • When there are no more free RValues, Ruby 1.9 sets FL_MARK on all active Ruby objects (marks)... • ...then relinks inactive objects (sweeps) into a single linked list (the “free list”)
  15. Copy on Write • When our production processes call fork,

    the new child process shares all memory with the parent • Copies are only made when changes are written, but marking objects as live on the objects themselves forces a write • Ruby 1.9.3 GC subverts native copy on write
  16. Let us not burden our remembrances with A heaviness that's

    gone. — The Tempest, Act V, Scene i
  17. Memory in Ruby 2.0 Header 0 1 0 0 0

    1 0 1 1 0 1 0 1 1 1 0 0 1 0 0 0 0 1 0 0 1 1 1 0 1 0 0 0 1 0 M M M
  18. Bitmap Marking • Ruby 2.0 includes a header at the

    beginning of each heap that contains a pointer to a bitmap representing the state of each object (1 == marked, 0 == unmarked) • The GC mark phase no longer modifies objects, allowing child processes to appropriately leverage copy on write
  19. How This Affects Unicorn • Unicorn creates N workers by

    forking itself N times (which is not a Bad Thing™) • Without copy on write, each worker spends time and memory making copies of objects that are marked by the GC but are otherwise identical • As N increases, the problem gets worse
  20. Time Trials • Loading the app in 1.9.3 invoked 122

    GC runs and took approximately 4.4 seconds • Loading the app in 2.0 invoked 66 GC runs and took approximately 3.0 seconds • In this case, 1.9.3 spent 47% more time collecting garbage
  21. #1: Upgraded to 2.0 • Leveraged copy on write •

    require is faster • Cool new features like Module#prepend, lazy Enumerators, refinements, and UTF-8 by default • It was about damned time
  22. #2: Profiled • Find and eliminate SOBs (sources of bloat)

    • Native GC profiling: http://ruby-doc.org/ core-2.0/GC.html • ruby-prof: https://github.com/ruby-prof/ ruby-prof
  23. #3: Tuned the GC • RUBY_GC_MALLOC_LIMIT (controls when we perform

    a full GC run; defaults to 8MB) • RUBY_HEAP_MIN_SLOTS (controls slots per heap, defaults to 10_000) • RUBY_HEAP_SLOTS_GROWTH_FACTOR (controls heap allocation rate, defaults to 1.8x)
  24. Credits && Further Reading • http://patshaughnessy.net/2012/3/23/why-you-should-be- excited-about-garbage-collection-in-ruby-2-0 • http://samsaffron.com/archive/2013/11/22/demystifying- the-ruby-gc

    • Ruby Under a Microscope (Pat Shaughnessy) • Ruby Performance Optimization (Alexander Dymo) • Koichi Sasada (http://youtube.com/watch/?v=92zMKGt7Qlk)
  25. Questions? eric_weinstein = { employer: 'Condé Nast', github: 'ericqweinstein', twitter:

    'ericqweinstein', website: 'ericweinste.in' } RUBYCONF2015 (40% off!)