"At first glance, Ruby and Python seem to implement garbage collection very differently. Ruby uses John McCarthy’s original mark and sweep algorithm, while Python uses reference counting. But when we look more closely, we see that Python uses bits of the mark and sweep idea to handle cyclic references, and that both Ruby and Python use generational garbage collection in similar ways. Python uses three separate generations, while Ruby 2.1 uses two.
This similarity should not be a surprise. Both languages are using computer science research that was done decades ago – before either Ruby or Python were even invented. I find it fascinating that when you look “under the hood” at different programming languages, you often find the same fundamental ideas and algorithms are used by all of them. Modern programming languages owe a great deal to the ground breaking computer science research that John McCarthy and his contemporaries did back in the 1960s and 1970s."
The reason why some platforms have "silly" choices like stop the world M&S GC, or interpreters with AST walking is, likely, that optimization wasn't a goal in the original implementation and it's hard to retrofit it while keeping compatibility.
EDIT: Better link: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.63....
That's pretty huge. That's several times larger than some other dynamic languages -- languages that have essentially the same features as Ruby.
Primitives are a bit bigger in ruby, but they aren't really primitives anyway (everything is an object).
For my perl rewrite p2 I need one single word per object on the stack. Common Lisps usually needs two words, the class pointer and the tagged value.
I really liked the visualization of the various GC's btw. Excellent work!
Large heaps mean long pauses when doing the "mark" phase of the generation holding most of the objects. That seems like a big problem that will increase as memory sizes increase.
Maybe the way I'm looking at it is too simplistic and there are better methods now. But even in Java, which has had plenty of time to work these issues out, I have seen issues with long GC pauses.
My impression right now is that GCs just don't scale to large heaps. To use more memory, you need to either manage memory yourself (which not a lot of modern languages allow), or increase the number of independent heaps (by using more tasks/processes).
Please enlighten me if I'm wrong here.
For better latency without such hiccups, the only solution I know of is Azul's Zing (http://www.azulsystems.com/zing/pgc) which really did away with larger pauses at least with our software.
Interesting; I'll check out the Azul one. I'm a little skeptical, but it might be an improvement. Did you see better throughput or just reduced latency?
Which, sadly, may or may not be what it encounters. There are still some usage patterns that can cause large amount of time being spent doing gc (without Zing).
Latency was greatly reduced, I cannot really say about throughput (except that it was not clearly worse at least).
In my potion-based GC (a stack-scanning, compacting, cheney two-finger GC) the avg GC needs 3ms, but it's not concurrent (multi-threaded) yet.
All of these can be considered real-time, i.e. < 15ms.
For bigger heaps the scans need to be incremental (saving and restoring GC state, which is easy with libgc or cheney).
The best java GC I found needed 150ms pause time. Good lisp's have real-time GCs.
Great post btw. I am looking forward to getting some production apps onto 2.1 in the coming weeks and seeing how performance characteristics have changed in the new release.
I recently upstreamed a warmup method for Rack::Builder you can also use for this purpose: https://github.com/rack/rack/pull/617
Now just when will Ruby get a Method or Tracing JIT?