00:00:16.320
Hey everyone, can you hear me? Okay, good, rock on! I think we'll go ahead and get started.
00:00:24.359
So yes, this is the Hitchhiker's Guide to Ruby GC. I tend to speak very quickly, so if I start talking too fast, just let me know.
00:00:32.160
I get excited because, you know, Ruby is exciting, and garbage collection is exciting.
00:00:38.360
If I start talking way too fast, you guys can sort of signal me, and that would be awesome.
00:00:45.960
Unlike Justin, however, I don't have any tenx insights for you.
00:00:51.480
I do make silly jokes like Aaron does, but unlike Aaron, I'm not going to offer you life-changing insights into the Ruby Virtual Machine.
00:00:58.840
My talk is, in fact, literally garbage— or rather, it's about garbage.
00:01:06.920
Speaking of garbage, did you guys hear about the controversy around the show Dirty Jobs?
00:01:12.479
Basically, it has something to do with the huge number of microservices that they're employing.
00:01:17.680
You can laugh if you want; at least someone did!
00:01:23.320
Yeah, I also don't enjoy my puns nearly as much as Aaron does, so I'm going to skip that.
00:01:30.119
Anyway, I should say I'm learning a lot at this conference—yeah, that was a terrible joke.
00:01:38.960
I'm learning a lot at RubyConf, and I'm trying to apply it immediately.
00:01:44.280
Gary had a great talk about ideology and belief, which is something I'm trying to internalize.
00:01:50.360
But you know, it's hard, right? We don't know what we don't know, and sometimes even when we do know it, we are unable to disarm ourselves.
00:01:58.360
This is my first time giving this talk; in fact, this is only my second talk ever.
00:02:04.560
I spoke earlier this year at RailsConf, so I'm going to try to face my own imposter syndrome.
00:02:11.680
Also, you'll see that my nervousness manifests in obnoxious slide transitions.
00:02:17.360
Anyway, my name is Eric Weinstein. I work at a company called Condé Nast.
00:02:22.920
I don't know if you've heard of Condé Nast; you're probably familiar with the various brands like Wired, The New Yorker, Vogue, GQ, etc.
00:02:29.519
I really enjoy writing Ruby. I write JavaScript a lot at work, so it's nice to be able to write Ruby for some of those projects and in my own time.
00:02:35.840
The Condé Nast folks are all using Ruby and Rails, so we are hiring.
00:02:43.840
Feel obligated to say that we're hiring.
00:02:49.959
And you might be interested in this strange hash and why it's a hash rather than an object, such as a person.
00:02:55.200
This foreshadows a lot of literary devices in this talk.
00:03:00.599
Also, obligatory self-promotion: I wrote the Ruby curriculum on Codecademy a couple of years ago.
00:03:06.360
I also wrote a book called Ruby Wizardry for kids ages 8 to 12.
00:03:12.480
There's a really great Birds of a Feather session organized by J. McGavin, who is doing a talk on method lookup later in this room.
00:03:18.480
I highly encourage you to go to that talk as well.
00:03:26.280
If you're interested in learning Ruby or teaching Ruby to kids, please come see me after the show.
00:03:31.840
No Starch Press has been delightful and is offering a 40% discount.
00:03:37.480
If anyone wants the book, use the code RUBYCONF2015 for 40% off.
00:03:43.840
Now, let’s talk about garbage collection.
00:03:49.840
There's a lot of mythology around GC and GC tuning, and it's not as bad as it seems.
00:03:56.360
For those unfamiliar, "Don't Panic" is emblazoned in large friendly letters on the cover of The Hitchhiker's Guide to the Galaxy.
00:04:02.560
This talk is as much for my benefit as it is for yours.
00:04:10.439
Part zero: because this is a computer-type conference, we should start at zero.
00:04:16.160
Ruby is not slow. Okay, sometimes it can be, but not for the reasons you think.
00:04:22.919
People may think their Ruby program is slow due to database issues or super-linear, nested loops.
00:04:28.639
Or they may claim Ruby is an interpreted language that can't possibly be fast and that we should all be using Go or Java.
00:04:34.280
What I've found is that when Ruby programs slow down, garbage collection is often in the culprits.
00:04:41.880
The object space and GC are a rich part of the language.
00:04:48.360
Not surprisingly, when there's a lot of richness, there’s a lot of nuance and performance bugs can hide.
00:04:54.560
The statement shown on the screen does make some operations slower than more compiled languages.
00:05:01.520
However, we have this architecture because everything is an object.
00:05:07.840
Let's take some time to talk about the history of garbage collection in Ruby.
00:05:14.300
First, we are talking about MRI, or CRuby.
00:05:20.100
We are not discussing Rubinius or JRuby, which have different garbage collectors.
00:05:26.550
I wanted to include them in this talk, but I only have so much time.
00:05:32.380
I also don't know nearly as much about them, but if you’re interested, come find me with your cool insights.
00:05:39.750
I may make comparisons to these alternate implementations when it makes sense.
00:05:47.200
Let’s talk about Ruby 187.
00:05:53.600
Ruby 187 uses tracing rather than reference counting for garbage collection.
00:05:59.760
Garbage collection traces involve searching for reachable objects in the object graph.
00:06:06.520
By traversing objects, we can find out if they should be marked as active.
00:06:15.200
If an object is unreachable, it is eligible for collection.
00:06:21.920
Ruby 187 used a simple mark and sweep algorithm.
00:06:28.500
Mark and sweep was invented by John McCarthy in 1958 or 1959.
00:06:35.160
It's astounding that Ruby has gone so far with such a simple garbage collection method.
00:06:41.960
In the mark and sweep method, Ruby allocates a certain amount of memory.
00:06:48.180
When there's no more free memory, Ruby looks for all the active objects, marks them as active, and sweeps the inactive ones onto a new list.
00:06:55.080
This is a broad overview of what Ruby is doing.
00:07:01.680
The important thing to know is that everything stops.
00:07:07.080
Reachability in the object graph can change during execution.
00:07:13.040
To mark and sweep effectively, the collector has to stop the world. If you hear 'stop the world garbage collection,' this is what they mean.
00:07:19.600
So, in 1993, we got improvements with lazy mark and sweep.
00:07:26.480
This improves stop times by sweeping in phases.
00:07:31.680
It doesn't affect the total time spent collecting garbage, but it reduces the longer pauses.
00:07:39.000
If you have a highly event-driven or I/O-driven application, not sitting for several seconds while collecting garbage is a big win.
00:07:46.920
However, both 187 and 193 subverted native copy-on-write.
00:07:54.240
This means that while it doesn't reduce the overall pain of stopping the world, it amortizes the pain over more sweeps.
00:08:00.480
In Ruby 2.0, we introduced bitmap marking.
00:08:07.359
Instead of marking objects directly, we now have a bitmap that represents the state of objects and their eligibility for collection.
00:08:14.240
This will be extremely important later because it allows us to use copy-on-write.
00:08:20.600
I'll be covering all these improvements in depth soon.
00:08:26.000
If, like Aaron, I don't die during this presentation, I'll be turning 30 in March.
00:08:32.000
I guess I’m officially entering old age.
00:08:39.680
Let’s talk broadly about Ruby 2.1. It has generational garbage collection.
00:08:46.480
There are two generations: a young one and an old one.
00:08:52.360
If an object survives three collections in 2.1, it becomes considered old.
00:08:59.360
This is based on what's called the weak generational hypothesis: objects die young.
00:09:06.000
There are many objects that appear and then are gone.
00:09:12.160
Because of this, it makes sense to do fast minor GCs frequently and the slower stop-the-world collections less frequently.
00:09:18.000
If you're interested in the RGC algorithm, Kai has a talk from EuroRuby a few years ago, which is excellent.
00:09:26.800
Now, let's go into more depth about 2.2.
00:09:34.000
We'll talk about copy-on-write, bitmap marking, and the tuning of garbage collection.
00:09:40.000
Let's start with two significant topics: incremental and symbol GC.
00:09:46.080
Symbol GC is something you might be familiar with if you’ve been writing large Rails applications.
00:09:53.240
There’s a notion of symbol denial-of-service: if you generate a lot of new symbols, they never get collected.
00:10:00.520
The new feature allows the collection of some symbols when they are no longer referenced.
00:10:07.200
However, not all symbols can be collected because Ruby internally generates symbols for method names.
00:10:13.600
If you dynamically generate many methods, each one getting their own symbol, that can lead to memory issues.
00:10:19.040
The really cool addition in Ruby 2.2 is the ability for incremental major GC.
00:10:26.120
Many of you might be familiar with tricolor marking.
00:10:33.120
The idea involves three types of objects: white, gray, and black.
00:10:39.720
White objects are unmarked, gray are marked and may refer to some white objects, and black are marked without referring to any white.
00:10:45.440
When starting, all objects are white, and alive objects are marked as gray.
00:10:51.360
We pick gray objects, visit all references, and mark them gray.
00:10:57.120
Then we change gray to black if they don't refer to white objects.
00:11:04.560
This algorithm allows us to sweep away unmarked objects.
00:11:10.080
However, there is a bug: if we create a white object and there are no gray objects referencing it, we can inadvertently reclaim live objects.
00:11:16.160
Right barriers help us prevent this issue.
00:11:23.240
For complicated reasons, there are insufficient right barriers in CRuby.
00:11:29.920
This leads to right barrier protected and unprotected objects.
00:11:36.960
The pause time corresponds to the number of living right barrier unprotected objects.
00:11:44.000
Most user-defined objects, such as strings, arrays, and hashes, are right barrier protected.
00:11:50.960
The actual fix involves checking all unprotected black objects before collecting white ones.
00:11:57.760
This helps prevent collecting objects we shouldn’t, where they are still in use.
00:12:04.600
So that's a brief history of GC in Ruby from 187 to 2.2.
00:12:11.480
Now let's talk about GC tuning.
00:12:17.360
If there's one takeaway from this talk, it should be: do not do it.
00:12:23.360
Or rather, for experts only, don’t do it yet.
00:12:29.360
This is a paraphrasing of Michael Jackson—yes, another Michael Jackson.
00:12:35.200
He said to avoid optimization unless you are certain of what you're doing.
00:12:41.680
There are a few variables you can modify to affect how garbage collection is performed.
00:12:48.480
These include the heap growth factor and max slots; lowering either of these will trigger more frequent young object garbage collection.
00:12:54.280
The default was around 1.8 for a long time. Adjusting these parameters can have unintended consequences.
00:13:01.440
Also, lowering the other parameters—like the malloc limit and max—can force more frequent allocations or collections.
00:13:07.760
Again, if you trigger many major garbage collections, you’ll find yourself just sitting around collecting garbage.
00:13:13.920
It's important to realize there are no silver bullets.
00:13:20.360
If you modify any of these variables without proper measuring, you might worsen the situation.
00:13:27.000
There is a lot of mythology around garbage collection that can lead to misguided optimizations.
00:13:33.680
If you decide to tune the garbage collector, measure everything carefully before and after.
00:13:39.440
Now, let me delve into the case study portion of the talk.
00:13:48.160
A couple of years ago, I was working at a company with a Ruby application that was very interesting.
00:13:55.000
It was not a Rails application, but rather a suite of several Sinatra applications that had been smashed together.
00:14:01.840
The way it worked is that users would browse to the site, and the Ruby application would make requests to several Java services.
00:14:09.600
These Java services communicated using JSON, which the Ruby app would inflate into hashes.
00:14:16.000
We ended up with huge hashes floating around, which led to people mutating them.
00:14:24.000
Someone got this crazy idea to orient our code around objects, suspecting it was a fad.
00:14:30.640
This approach tanked performance and caused memory problems.
00:14:37.040
Everyone was struggling; New Relic showed we were constantly running out of memory.
00:14:45.040
We realized that by moving towards object-oriented Ruby, we had inadvertently shot ourselves in the foot.
00:14:51.920
Let’s talk about memory and how it all relates to Ruby 1.9.
00:14:58.560
In Ruby 1.9, Ruby objects are 40-byte rvalue structures allocated into 16-kilobyte heaps.
00:15:05.200
If you're using 64-bit architecture, you can get about 400 Ruby objects per heap.
00:15:10.920
At the start, Ruby gives you approximately 150 heaps.
00:15:17.520
You can see through the object space that we were creating way too many objects.
00:15:23.200
We started the web server and saw we had over half a million live objects right off the bat.
00:15:29.680
This seemed completely wrong for a Sinatra app.
00:15:36.800
We were making many more objects than needed for a simple application.
00:15:43.200
Ruby stores small values directly on the object up to 23 characters; longer values are stored as pointers.
00:15:50.760
When creating many objects, the cost can rise dramatically.
00:15:58.000
And, of course, when Ruby goes through garbage collection, linked lists can become cumbersome.
00:16:04.880
When there are no more free R values, Ruby sets the FL mark on all active objects.
00:16:11.439
This marks objects during the sweep phase by relinking inactive ones into a free list.
00:16:18.040
With copy-on-write, parent and child processes can share memory until a write occurs.
00:16:24.840
If marking occurs directly in an object being forked, this leads to issues with memory management.
00:16:31.000
Increases in the number of forks lead to an increased collection of white and black objects.
00:16:37.000
As the number of unicorns increases, the GC issues compound.
00:16:44.240
This is not an inherent flaw with Ruby, but rather a result of the complexities of GC.
00:16:51.560
So, what's not inherently bad?
00:16:57.920
Memory management and bitmap marking have been implemented.
00:17:04.280
Now every heap has a header pointing to a bitmap.
00:17:10.960
This simplifies the marking process, allowing us to leverage copy-on-write effectively.
00:17:16.560
Finally, we need to reduce overhead and unnecessary writes.
00:17:24.160
Unicorn does well at managing processes, but we must be mindful of how many unicorns we run.
00:17:30.440
Every GC run incrementally improves things.
00:17:36.800
Loading the application in Ruby 1.9 invoked 122 GC runs and took about 4.4 seconds.
00:17:43.680
In Ruby 2, simply loading the app only invoked 66 GC runs and took around 3 seconds.
00:17:49.600
This shows a significant improvement that we needed to take advantage of.
00:17:56.800
We upgraded to Ruby 2, which leverages copy-on-write.
00:18:02.160
This also means we adopt newer features while maintaining performance.
00:18:09.120
Number two: we profiled extensively, trying to identify and eliminate the sources of bloat.
00:18:15.920
We utilized the Ruby 2 documentation as well as the Ruby Prof gem.
00:18:23.920
We tuned the GC with variables such as malloc limit, managing trigger points for full GC.
00:18:31.920
Making informed adjustments to heap slots afforded us better allocation without pitfalls.
00:18:38.320
This tuning allowed us better growth and fewer stop-the-world pauses.
00:18:45.920
As we dive deeper into Ruby optimization, we addressed a wide net of issues.
00:18:52.960
Significant credit goes to various authors and developers who provided resources, insights, and referrals.
00:18:59.040
Many thanks again for your time and taking a moment to listen to my talk.
00:19:05.600
And of course, obligatory self-promotion: that's the book I wrote.
00:19:12.160
If you're interested in Ruby Wizardry, please reach out to me or look for it through standard channels. Thank you!