Wednesday, October 05, 2016

RubyConf 2015 - The Hitchhiker's Guide to Ruby GC by Eric Weinstein

me okay?
Good?
Rock on, I think we'll go ahead and get started.
So yes, The Hitchhiker's Guide to Ruby GC.
I, like Justin, speak very quickly,
so if I do speak, I get going, I get excited,
cause Ruby's exciting and garbage collections is cutting,
if I start talking way too fast, you guys
could just kinda like, I don't know like,
some kind of, something,
yeah, exactly, please.
That'd be awesome.
So, unlike Justin, however,
I don't have any TenX insights for you.
I do make silly jokes like Aaron does,
but unlike Aaron, I'm not gonna offer you
life changing insights into the Ruby virtual machine.
My talk is in fact, literally garbage, or about garbage.
Speaking of, did you guys hear about
this controversy about the show Dirty Jobs?
Maybe it was just me, basically I guess
it has something to do with the huge
number of microservices, that they're employing.
Thank you, somebody laughed at it, alright good.
(laughter) Good, good.
I also don't like enjoy my puns
like nearly as much as Aaron does,
so I'm just gonna put a line through that.
I'm not gonna do that anymore.
(laughter) Anyway, alright cool.
I should say I'm learning a lot.
That was a terrible joke.
I'm learning a lot at this conference,
I'm learning a lot at RubyConf,
and I'm trying to apply it immediately.
Gary had a great talk about ideology and belief
and it's one of those things that I'm
sort of trying to internalize.
But you know, it's hard, right?
We don't know what we don't know,
and even when we know it,
sometimes we are unable to disarm ourselves.
So this is my first time giving this talk.
This is actually my second talk ever.
I spoke earlier this year at a RailsConf.
So I'm going to try to confront my own impostordom
and also channel Aaron and not die.
And also, you'll see that my nervousness manifests
in obnoxious slide transitions, so there's that.
Anyway, so, my name's Eric Weinstein.
I work at a company called Condé Nast.
I don't know if you guys have heard of Condé,
you're probably familiar with the various brands,
so there's Wired, the New Yorker, Vogue, GQ, etc.
I really like 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 on my own time as well.
The Condé Nast entertainment folks
actually are all using Ruby and Rails.
So we are hiring, I feel obligated to say that we're hiring.
And you might be interested, sort of, by this strange hash,
and why is it a hash and not an object, like a person.new?
And that is foreshadowing, so you will see.
There's a lot of literary devices in this talk.
So, also obligatory, self promotion.
I wrote the Ruby curriculum on Code Academy
a couple years ago,
and I also wrote this book called Ruby Wizardry.
It's for kids ages 8-12.
There's a really great birds of a feather
organized by Jay McGavren
who actually is doing a talk on method lookup
later in this very room, so I highly
encourage you to go to that talk as well.
And if you're interested in learning Ruby
or teaching Ruby, kids Ruby, nouns
please come see me after the show.
And I am, so no starch has been delightful
and is offering a 40% off so if anyone
does want the book RUBYCONF2015,
and it'll be 40% off I think for the week.
Garbage collection, so there's a lot of mythology
around GC and GC tuning and sort of how it all works,
it's actually not as bad as it seems.
And for those not familiar the don't panic!
Is kind of like emblazoned in large friendly letters
on the cover of the Hitchhiker's Guide to the Galaxy.
This also as much for my benefit as yours.
(laughter) So,
part zero, cause you know this is
a computer type conference and we should start at zero.
Ruby is not slow.
Okay yes, sort of, sometimes depending, it can be.
But not for the reasons that you think.
So people will say okay well my Ruby program
is slow it must be a database thing.
Or it must be there's some superlinear
crazy four-deep nested loop and I'm doing
something bananas in there that I shouldn't be doing.
Or Ruby's an interpreted language
and can't possibly be fast and we should all be
using Go or Java or something.
And so really what I've found
is that when Ruby programs slow down
these aren't always or often the culprits.
The object space in GC is actually
a rich part of the language.
Not surprisingly, when you have a lot
of richness there's a lot of nuance
and performance bugs can hide in there.
These things are true, that are on the screen.
They sort of do make some operations slower
than you know Java or compiled language.
But the reason that we have all this
for better and for worse is cause
everything is an object.
Not everything you know blocks,
we're not objects but, you get the idea.
So let's take some time and talk
about the history of garbage collection in Ruby.
First we're talking about MRI,
CRuby, we're not talking about
Rubinius or JRuby, which have different
garbage collectors, I'd wanted to include them
in this talk but unfortunately I only have so much time
and they ended up on the cutting room floor.
I also don't know nearly as much about them,
but if you are interested in Rubinius or JRuby
or have some cool stuff to share
please come find me.
I make make comparisons to these alternate implementations
if the comparisons make sense so they might
kind of make guest appearances
but by and large the talk is about MRI.
So let's talk about Ruby 1.8.7.
1.8.7 uses tracing,as opposed to say reference counting.
GC and GC traces our sort of like,
you're looking for reachable objects
in the object grab.
So if by traversing objects I can get to one
that object is reachable, and great.
That should not be collected
there are still references to it.
As demonstrated in the graph.
And if they're not reachable
if an object is unreachable it is eligible for collection.
1.8.7 uses very simple mark & sweep
mark & sweep was invented by,
John McCarthy that's right.
It's not Alexander Graham Link,
for LISP in 1959 and it's astounding that Ruby
had gotten, had gone so far with just simple mark & sweep.
Garbage collection, and garbage collection
works this way in mark & sweep.
Ruby will allocate some amount of memory,
we'll talk about details in a little bit.
When there's no more free memory,
Ruby says okay great, I'm gonna go through
I'm gonna look for all of the
active objects that I can find,
I'm gonna mark them as active,
and then anything that's inactive
I can sweep onto a new list and that's
where I'll go when I need more stuff.
So this is what Ruby is doing.
This was a fun animation, I like the arrows moving.
Anyway, the important thing to realize though
when you're marking & sweeping
and marking & sweeping and having a great time is that
everything stops.
Because reachability in the object graph
can change from you know,
when you are marking and then sweeping.
It can change while the program is executing
and it does change while the program is executing.
So in order to mark & sweep
the collector has to stop the world.
So if you hear people talking about
stop the world garbage collection.
major garbage collection this is what they're talking about.
Everything stops, Ruby marks, sweeps,
everything's great and then continues execution.
So in 1.9.3 we got some improvements.
We got lazy mark & sweep.
And this is an improvement because it increases
or rather reduces the pause time by saying
okay I'm actually just going to sweep in phases.
It doesn't really do anything to the amount
of time you spend collecting garbage
but if you say I'm gonna garbage collect
and it's gonna take half a second,
and I'm gonna do it like ten times
instead of one second and doing it five times,
you know you don't get these unacceptable hangtimes
while garbage collection is happening.
And if you do have like a highly evented
or io driven application, you have a web server,
you have something with a GUI,
you can't just like sit there for a couple seconds
while you're collecting garbage?
This is a big win.
Unfortunately 1.8.7 and 1.9.3 both
submit native copy-on-write which we will
also talk about it'll be on the quiz.
That's a huge lie there's no quiz.
Just more information.
Anyway so this doesn't reduce the kind of overall
pain of stopping the world but it does sort of amortize
that over more sweeps.
So in 2.0 we got bitmap marking.
So we are no longer marking objects directly
rather we have a bitmap that represents
the state of objects and their eligibility for collection.
This'll be extremely important later
because this is what sort of allows us to use
copy-on-write and have a great time.
I'll be covering all these in depth pretty soon
I just kind of want to show you
where we're going on the journey.
That is this talk.
So yes if like Aaron I don't die
during this presentation I'm gonna be turning 30 in March,
which is kind of like when I become old I guess,
I guess each decade is a collection.
I'm talking broadly about Ruby 2.1,
so we have generational garbage collection,
two generations a young one, and an old one.
And if you survive three collections
then 2.1 you become old.
And this is sort of based on the
weak generational hypothesis, objects die young.
You have a lot of objects that appear
and do something and then are gone.
And so based on the fact that objects
tend to die young, it makes sense to do
minor GC frequently and the slower
stop-the-world collections less frequently.
If you're interested in the RGenGC algorithm,
Koichi has a talk from EuRuKo a few years ago.
Which is excellent and there's a link
at the end of this presentation.
So that was sorta the history of Ruby 1.8.7
1.9 and then 2, nice 2.1 and now we're gonna talk
a little bit more in depth about 2.2.2
but it's 2.2 in general.
And we'll talk a little bit more about
copy-on-write, bitmap marking
all the stuff I mentioned, but here
I'm just gonna focus quickly on two things,
incremental and symbol GC which we have in 2.2.
We'll also talk a little bit about
garbage collection tuning.
Which is scary and amazing.
So Symbol GC, you have read
has an excellent writeup on Symbol GC.
And if you've read that or if you've been
writing large Rails app, Rails and Ruby applications
you're familiar with this notion
of like symbol denial service, right?
You have a bunch of symbols, they never get collected
because they just don't, they're alive forever.
And if you were to do something strange
but not on the face of it's silly,
which is to let users generate something
that will create a symbol, and someone creates
thousands and thousands and thousands of symbols
you will eventually run out of memory.
That is bad, it turns out.
And so what we have now is the ability
to reclaim some symbols, not all of them,
and allow those to be collected
when there are no more references.
When they're no longer needed.
And the reason I say some not all
is because Ruby internally will generate
symbols for say method names,
and you could easily DOS yourself
again if you did something like
I guess dynamically generate
a lot of methods that all get their own symbol.
That's unusual, anyway, so we have that which is nice.
But the really cool thing is we have incremental major GC.
Again there's another algorithm that
there's excellent blog posts and papers
on and I encourage you to look up.
The cool thing here is this sort of tricolor marking.
And many of you are probably familiar with this.
But the idea is that we have three types of objects.
We have white, which are unmarked,
grey, which are marked, and may refer to some white objects,
and then black, which are both marked,
are marked but do not refer to any white objects.
And here's how the algorithm probably works.
You say okay, all of my objects are white.
Everything that's alive, obviously alive,
like things on the stack, those are grey.
And I'm going to start picking,
I'm gonna pick one grey object,
and I'm gonna keep doing this for all of them.
I'm gonna visit every single reference,
and color that grey.
And then when I'm done with this
I'm gonna go back to that first grey object
and say okay this is now black,
since it does not refer to any white objects.
I'm gonna keep doing this and keep doing this
until I only have black and white objects.
And this tells me okay great, I have black objects
which are obviously alive
and white which are obviously available for collection.
So I'm gonna go through and sweep,
and that's sort of how the algorithm works.
This third part, the part where we go through
and we do all the color changing
and we change the original node back to black,
this is sort of what's going on
in the incremental part of the algorithm.
Like Ruby will do some execution,
and then it will do some sweeping
and then some execution, and so on.
But there is a bug.
And I learned this from Aaron, emoji.
So yes I'm learning a lot at this conference.
I was gonna make it like dance around
but like eh, that was a bad idea.
So anyway so what's the bug?
The problem is what if you have a white object appear
so we create a new object, and there's no
grey objects with references to it,
because there are only black and white objects,
we are going to inadvertently reclaim
this live object by mistake.
It will be white when the algorithm runs,
the next time it finishes executing
some Ruby code, and says oh,
this is a white object, it is available
for me to collect, and our live
object that we wanted is gone.
This is a bad thing.
So what do we do, he said.
So there's this cool thing called Write Barriers.
They are super effective.
You know it because it says so on the screen.
Basically,
for a lot of complicated reasons
there are insufficient write barriers in CRuby.
And that is a think you should ask Koichi about
or somebody who knows much more about
write barriers than I do.
I am not unfortunately a write barrier scientist.
But basically to sort of circumvent this we now have
write barrier protected and write
barrier unprotected objects.
And what we, the plan here is okay
I'm gonna, because the pause time
is relative to the number of living
write barrier unprotected objects,
and most objects are user-defined
one strings, arrays, hashes, things like that.
Are write barrier protected, the pause time
is not gonna be that bad.
So what is the actual fix?
If we're like okay this is not
gonna be that tricky, we can apply
this to kinda fix the problem.
Essentially after all the black & white
objects are identified,
but before we collect the white ones,
we wanna go and say okay
I'm gonna scan from all of the
unprotected black objects we can actually guarantee
that the ones that are protected are managed,
and just kind of do another check.
Like has anything basically changed,
have new white objects appeared
since I last checked?
And by doing this we kinda say
okay great now I'm not gonna have
this bug where I inadvertantly collect
objects that I shouldn't.
Or I'm not gonna have a problem
with losing things that I didn't
really wanna allow to be collected.
So that is sort of a brief history
of GC from 1.8.7 to 2.2.
We can now talk about GC tuning.
And if there's sort of, if there's one TL;DR
if there's one thing you take away
from this talk, it should be this slide.
Do not do it.
Or rather, for experts only, don't do it yet.
And this is a paraphrasing of Michael Jackson.
Not the Michael Jackson, Michael A. Jackson.
Well I guess he's also a Michael Jackson.
But basically he said yes, (laughter)
in terms of program optimization,
any kind of optimization, don't do it,
and if you really know what you're doing
okay fine but don't do it yet.
So what happens if we decide, we're just going to try this?
So here are a few variables that you
can modify in Ruby to sort of affect
how garbage collection is performed.
These first two, heap_growth_factor
and heap_growth_max_slots
lowering either of these will trigger
more frequent young object garbage collection.
You're essentially saying hey, when I
get new heaps, which we'll talk about in a second,
you can I think the default's in 1.9 or 2,
it may still be 1.8, but at one time it was 1.8
so you know you have 10,000 and then
another 10,000, and then okay it's gonna be 1.8
I'm gonna get 18,000.
And this will say as you need more memory
great here's larger and larger chunks of memory to use.
So you can lower this and then you run out of memory
faster and then you're forced to collect.
The same thing with the growth_max_slots.
The latter three, malloc_limit, malloc_limit_max,
and the limit_growth_factor, lowering
any of these three will tell Ruby
that it's not allowed to allocate as much
off-heap memory before running minor GC.
Which also triggers more frequent object allocations.
Or collections, rather.
And you can sort of do this with old malloc,
which is I think the namespace for larger, like,
major GC, but this is probably a bad idea
because as we've seen, if you start
triggering a lot of major garbage collection,
you're gonna be sitting there collecting garbage,
instead of you know, sending emails to your users
or something of that nature.
The important thing to realize
is this is not a silver bullet.
There are no silver bullets.
There are a lot of werewolves.
There are no silver bullets.
It's a bad time, so there's, you know
this is not as helpful as it may seem.
There'll be times when you say yes,
there's a lot of objects there's a lot of stuff
that's going, we're churning, and garbage collection
is a problem, we've identified this
is kind of a thing that we should do,
you can inadvertently make things worse for yourself.
By modifying these variables and not doing a lot
of measuring and being very careful.
Right this is also one of those myths
it's very attractive, it's the source of those like
weird old tricks and tips about x, y, z.
There's a lot of mythology around things like
bittwiddling and garbage collection
and I advise you that if you do decide
yes, I'm gonna tune the garbage collector,
this is something you just wanna measure
a lot beforehand, after, make sure
that what you're measuring is actually
what you're measuring because
often times that is not the case.
Just kind of, proceed with caution.
So, that said, we're gonna move on
to I think the sort of the bulk,
which is this case study part three.
So, couple of years ago I was working
at a company, and we had a Ruby application.
It was interesting in so far as it
was not in a Rails application.
It was a suite of seven Sinatra applications
that had been sort of smashed together.
In interesting ways.
And the way that this worked is users would
browse through the site, they would
go to the web layer, and the Ruby application
would make requests to a number of Java services.
The Java services all spoke JSON,
so the Java service would say hey,
I have some JSON for you,
and Ruby would say awesome, that's great,
I love JSON, and would kind of inflate
them into hashes, and then pass them around.
So you would just have these huge hashes
floating around and you know,
people would just start picking stuff out of them
or mutating them, cause that's a thing you can do.
And it was a bad time.
And so someone got this really crazy idea,
and said why don't we take some objects
and orient our code around them?
And I suspect this is a fad,
but we were like alright, we'll try it.
Because this hash thing is not working out,
and now, you see why earlier,
there was a hash and not a person
that got newed up because hashes are a thing.
So, anyway.
We were talking about doing this,
and we started doing it,
and performance tanked.
We were constantly running out of memory,
we were sluggish, we were having
all sorts of issues in new relic,
everyone was having a hard time,
the business was yelling at us,
we were yelling at each other,
and we couldn't figure out why
we decided to write object oriented Ruby,
but we shot ourselves in the foot.
So let's talk about memory.
And again this is all in the context of 1.9.3,
at the time we were writing Ruby 1.9.3
and we were sort of trying to figure out
what's going on and this is sort of how
the switch to 2 happened.
So the memory model looks something like this.
Ruby objects are 40-byte RValue structures,
which Ruby allocates into heaps of 16KBs each.
This is for 64-bit architecture, I suppose
for 32-bit it's gonna be somewhere
like 20 or 24 but we'll say for argument's sake
that we're all using 64-bit machines.
So you're gonna get somewhere around the order
of 400 Ruby objects per heap because you have 16,000
bytes divided among you have 40 bytes per RValue structure.
And so, Ruby will give you about 150 heaps to start.
Which is awesome.
And so you can kind of test this yourself here,
we're inside this Sinatra app,
and you can see the object space,
there's somewhere, there's about 408
objects per heap.
So.
In that 400 ballpark.
And it turned out we were making
a huge, and this is a little bit hard to read,
I apologize, a huge number of objects.
So at the top you see bxr console
it's just my alias for bundle exec rake,
the console task starts IRB with the application loaded.
We say okay GC_start, great,
and then GC_stat, and the interesting things in here
are we see the number of total GC runs.
We see the number of heaps with at least one used slot.
The heap length is the total number of heaps.
And heap increment is how many more
heaps to ask for, which I think yeah in this,
version of Ruby it was 1.8 times the previous number.
But the most interesting thing,
is this, the heap live number, where
there were half a million objects.
Just to start, we weren't even doing anything.
We just started the web server and said
hey how many objects are there.
There's half a million.
I would say that for comparison,
the average Rails app would be somewhere
closer to 400,000 but that's sort of a nonsense
statistic right, like there's no average
Rails app we all have different business needs,
we have different versions of Rails,
we're running different versions of Ruby,
or different Ruby implementations.
Different web servers like Puma or Unicorn
But this seems completely wrong,
for this Sinatra app, or this like
smashed together Frankenstein's monster Sinatra app.
And we weren't even using ActiveRecord
we were just you know, talking
to the services and getting responses.
So we, let's talk a little bit about these RValues.
For small values, it restores them directly on the object.
And you might've heard, via Pat Shaughnessy
who has an excellent book, Ruby Under The Microscope,
that there's in and this is very interesting
again it's one of those weird old tips
right, it's like, you don't want strings
that are over 23 characters.
This is crazy, it doesn't matter.
If you actually really do care
about that level of performance
and optimization, please come find me
after the talk because I have a tremendous
amount to learn from you.
That would be awesome, but, essentially,
what happens is yeah, 23 characters
here you have the value directly 24 and larger,
you have a pointer to some other location memory
and this is somewhat slower.
So for large values,
for whether it's a string or a hash or whatever,
the value is actually a pointer.
So the RValue itself has a flags field,
which contains FL_MARK, which we'll talk about.
Object contents, which is the value,
or it's either the actual object
or it's a pointer to one,
or and you have next, which is a pointer
to the next RStruct.
And there's RString for strings,
RHash for hashes, RObject
for custom objects and user space.
And these are all the same size.
So we talked about heaps.
So in 1.9.3, we were seeing
we had 10,000 slots.
Which was one heap.
We got the second heap, which was another 10,000 slots.
And every time we needed another heap,
we were multiplying by 1.8.
So the third time Ruby says hey I need more memory,
great, we get another 18,000 so we have 38,000
after the initial 20,000.
And then if we have to ask again
we go from 38,000 to 106,400
because we're doing this kind of doubling
almost, this 1.8 factor.
And this could be tricky if, and this is another
case for tuning, and this is one thing
that we did do, because you know,
I really only need like 50,000 objects.
And so I ask for, or 50,000 slots rather
and I ask for more, and now I have 106,400.
And I'm never actually going to use these.
And you realize you know,
Ruby is not going to give this back to the operating system
until the process exits, so.
Asking for more memory than you need
can be tricky and there's a case
to be made for doing the kind of tuning
I warned you about.
With that regard.
So let's talk more about Mark & Sweep.
So Ruby heaps comprised linked lists of RValues,
linked lists were in fact invented by Alexander Graham Link.
In 1836.
I realize that if you didn't see Aaron's doc
this just sounds like crazy stuff
so I'm sorry about that.
So when there are no more free RValues,
Ruby 1.9 will set FL_MARK on all active Ruby objects.
This is the marking phase,
and then relinks, kind of sweeps them
into a single linked list, and this is called the free list.
Here's the free list,
so you can kind of imagine it in your mind,
you have a linked list, marked objects,
some are not marked, and the ones
that are not marked are available
to be reclaimed, so they get swept,
and used again.
I just love that animation, anyway.
So copy on write.
We talked about this a little bit ago.
Copy on write, the idea is,
when your production process,
or any process calls fork, the new child process
shares all memory with the parent,
and then copies are only made when a write is forced.
Which is cool, this is great.
You don't actually have to write anything,
unless there's something different.
And so the fork process and the parent,
as long as you're not mutating stuff,
they can share memory and it's
you know you have this sort of persistent
data structure and it's really cool.
The problem is if you mark an object directly,
and say you are marked, you are available.
And then there's you know, a child process
somewhere that object is not marked.
So you have a marked and not marked,
you have these objects that sort of proliferate
that only differ in their eligibility for collection.
And this is bad.
You kind of get a proliferation of objects,
this kind of subversion of native
copy on write,
and it's not necessarily true in all places,
on Linux boxes and production,
you know, on your Linux machine or OSX machine,
they do leverage copy on write, we'll assume
that it's true everywhere, there are some instances
where it's not, but basically now we have
all these objects floating around
and for a web server like Unicorn
that does concurrency via forking,
the more Unicorns you have, the worse
your problem gets, because you're
constantly writing new objects.
So this is the Iridad portion
of the presentation you can tell,
your friends and colleagues and coworkers
that you learned something from my talk.
Which is a quote from Shakespeare.
So this is from The Tempest, the idea being
okay we don't need to do all these writes,
we don't need to have all this, so let's not.
And so this leads us to memory and bitmap marking
as mentioned before in Ruby 2.0.
So every heap now has a header,
that points to a bitmap, and you can kinda,
it's a little hard to see but these ones and zeros
correspond to marked or not marked
in the actual heap slots, so you
no longer have to mark an object itself as marked.
You can just update the bitmap
and that bitmap keeps track of who is available
for collection and who is not.
So this header is one is marked and zero is unmarked.
And so Ruby 2.0 with this header just kind of simplifies
this and allows us to actually leverage copy on write.
The GC mark no longer modifies objects and we only
have one object, if it doesn't change
in terms of its eligibility for collection
we don't have to write it among
a bunch of different processes
we just have the one.
And is mentioned, because Unicorn
manages everything by forking,
the more Unicorns you have,
the worse the problem gets.
Now this is not a bad thing.
You know, Unicorn is a popular web server,
forking is not intrinsically bad,
Unicorn is not intrinsically bad,
Ruby is certainly not intrinsically bad.
Ruby 1.9.3's garbage collection
algorithm was inadvertently doing a bad thing.
Which has been fixed.
Which is great.
So as mentioned as it increases,
the problem gets worse.
So here are some numbers,
hopefully they're somewhat illuminating.
Loading the application in 1.9.3 invoked
122 GC runs and took about 4.4 seconds.
Changing nothing else, simply loading the app in 2.0
invoked 66 GC runs and took about three seconds.
So in this case 1.9.3 and we were looking at the GC stance
and kind of instrumenting and carefully checking
and there was some variation in terms
of Gem versions and stuff like that,
we tried to get all that jiggle out.
And it was about 47% more time
simply collecting garbage in this application.
So given this information, what did we do?
That was much faster than I expected.
So number one, is we upgraded to Ruby 2.0
so we could leverage copy on write,
turns out require is also faster.
It's hard to see the line there,
this was new at the time, it's not
so new now, but there were cool features
like Module#prepend, lazy Enumerators,
refinements, and there's a talk about refinements,
which is awesome, unfortunately you are missing it,
but there will be a recording, so that's good.
And it was just about time.
Like Ruby 1.9.3 reached the end of life in February
and you know we were talking about this
I think the previous December.
So we had really a couple of months
to really get racked up, and yeah.
So that was the first thing that we did
was just make sure we were on Ruby 2.0.
So another takeaway is just update Ruby.
So this Christmas you're getting Ruby 2.3
and it will be awesome.
Number two is we profiled, so I mentioned
that we tried to find and eliminate sources of bloat,
we spent a lot of time doing native GC profiling,
this is the Ruby 2 docs, there's also
docs for 2.1 and 2.2, I encourage you to take a look
at GC and sort of the methods that are available there.
There's a lot of information about what Ruby is doing.
Much like how Aaron kind of showed in his talk
like how you can kind of pull apart
and see all the instructions in that
YARD was executing, you can also
go through and see what objects
are being allocated, how long they're alive
how many slots you have, things of that nature
and it's really very cool.
We also use the ruby-prof Gem, which helped us
and I can say, I don't have enough good things
to say about it, it's it was awesome.
And the third thing yes, I know I said don't do it,
but we reasonably sure we knew
what we were doing and it turned out
well so far so good.
Basically we tuned the GC.
So the three variables that we touched there
was the Malloc_limit, which controls
when you perform a full GC run,
the default was 8MB, which I think was set
it was chosen in like 1995, which made a ton of sense
for 1995 but didn't make sense for us.
So we tuned number one to get more memory
and sort of punt on full stop the world GC because
we could, you do more stuff before we had
to actually do that.
Number two, the Heap_min_slots,
that controls the slots per heap
which defaults to 10,000.
We tuned it a little bit to get more objects per heap.
I wanna say we only modified it
to about maybe it was 12,000 something of that nature.
And then Ruby 3, sorry number three,
Ruby_heap_slots_growth_factor as mentioned,
the growth factor was 1.8 so we
were to ask for more and more and more
we kind of looked at the graph and realized
that we were kind of plateauing a little bit
and we didn't really need to do 1.8
I think we collected 1.1 and 1.2.
So again lowering that in Ruby 2
will give you more frequent kind of GC runs
for the young generation, for the you know
minor GC, you know have the full stop the world stuff.
So some credits and some further reading
I'm deeply, deeply indebted to Pat Shaughnessy
who I said has an excellent book
Ruby Under A Microscope, was very helpful
in kind of pulling Ruby apart and seeing what was happening.
Sam Saffron has written a great post,
Demystifying the Ruby GC it's a couple years old
but it's still a great read.
Pat's book, Alexander Dymo has a book
called Ruby Performance Optimization.
Which goes much more in depth in the stuff
I was kind of talked about at the beginning.
Where a lot of the issues that we see
in our Ruby applications have to do
with the richness of the space and garbage collection
and sort of what is around, and is less related
necessarily to you know, bad algorithms
or bad datas queries although
those things do exist.
And then again, Koichi's talk is just astounding
I encourage you to look that up as well.
Blog post on the Heroku Engineering blog,
videos on Youtube, they're all excellent.
So really thanks so much for your time,
I really appreciate your taking time from
the RubyConf to come listen to me talk about stuff.
And again I guess obligatory, shameless
self-promotion, this is me, this is who I work for.
That's the book that I wrote, and yeah.
Thanks so much. (applause)
I'm sorry I don't understand the question.
Oh out of band GC, that's a great question.
So that was something that we were trying
to figure out for this application,
so the question is, out of band GC.
What do I think about it?
We, the idea is you essentially tell
the garbage collector, you're not in charge
of when you run, I will tell you
when you're allowed to run.
This is a thing that is useful
if you have something like a web application
where there are requests coming in
and someone could be in the middle
of a very complicated request
and Ruby will say, hey timeout
I'm out of memory I need to collect
I'll be right back, and then just do a major
garbage collection right in the middle.
You don't want that to happen.
So the idea is you say, I'm going
to only allow garbage collection
in between requests, like maybe
every ten requests or every 100 requests
or every 300 requests, that's when I'm gonna like
do some garbage collection and then back to the show.
For Unicorn at the time the tools available
were Unicorn worker killer, and I think
there was another out of NGC Library
that were using that sort of like allowed us
to maintain memory well it's kind of sniping
big bloated old Unicorns cause that would happen
since you were telling GC not to run anymore.
Like I will tell you when you're allowed to stop.
And it was a lot of overhead, doing that memory management.
Like you essentially give up all of the nice, cool like
automatic memory management that Ruby gives you
and it's a lot to fit in your head.
So while I think it's, there are certainly times
when it makes a lot of sense,
and it can be very very useful,
we found it was too hard for us
to get right, which does not mean
it's not too hard for you and your team to get right.
How long should a major garbage collection take?
That is an excellent question.
I feel like I,
I feel like I don't know the answer.
So, major garbage collection like certainly
you don't want to be like spending
several seconds collecting garbage.
As Gary mentioned in his talk yesterday
like this was sort of like why he was not convinced
that garbage collection was necessary
because garbage collection in 2002
was taking several seconds, and this was unacceptable.
With the newer stuff in 2.2 and the ability
to kind of incrementally do it,
I feel like there's the applications
I've worked on it was like less than a second,
like more than a second and we were kinda like
oh this is taking a long time like what's actually
going on, but I think it's gonna have to depend
on your application and your needs yeah.
Where do you, oh the book yes,
like I said the flashing buy now button.
So you can get the book from your local bookseller
it's published by no starch pess,
it's available on Amazon, it's available at Barnes & Noble.
I wish I had copies with me but they were,
they were extremely heavy, like it's
something like 340 pages, it's surprisingly big.
There are illustrations, so, yeah
it's pretty cool I'd be happy to show anybody
like a PDF for some sample pages on my machine,
later today, like I said I wish I had a copy
but I'm not good at marketing, it turns out.
Correct, yeah so this is a yes, to be clear
that's exactly right, that's a good question.
So the question is, this code
is for no starch, and that's correct.
So this is not something you can use
with any bookseller, if you go to nostarch.com
and you say I wanna buy Ruby Wizardry
there's a little box that says do you have a promo code,
and if you put in RUBYCONF2015,
it will be 40% off.
Like I said it's for kids ages 8-12,
certainly as young as six or seven
if they've got, you know if they're
really motivated and their parents
are willing to help, cause it can be kinda tricky.
And I suppose up to high school,
but it's sort of like the Phantom Tollbooth
like there's a time to read it,
and that time is not maybe when you're 16.
But I've had adults tell me that
you know, they liked it, so.
If you wanna learn Ruby,
maybe it's helpful for you too.
Rock on, well again, thanks so much.