Rubinius for the Layman, Part 1: Rubies All the Way Down
This is part one of an ongoing series about Rubinius:
- Rubinius for the Layman, Part 1: Rubies All the Way Down
- Rubinius for the Layman, Part 2: How Rubinius is Friendly
In January, Antonio Cangiano wrote an article titled Why Engine Yard, Rubinius and Merb matter. In his article he discussed the financing of Engine Yard. More specifically, he discussed why EY was not just another irrelevant startup being funded by starry-eyed investors. Au contraire! Engine Yard is already recognized as a solid hosting service provider for Ruby on Rails, and has a customer base. He then went on to discuss how they are going to use the money to fund the development of Merb and Rubinius by hiring the main contributors and paying them to work on these projects.
These two projects are very important for the future of the Ruby ecosystem. Rubinius is an attempt at a very powerful Ruby VM while Merb is an attempt at a solid alternative to Rails, with its own distinct advantages. It’s great to hear that EY has taken it upon themselves to basically put both projects on steroids.
Being interested in the different Ruby interpreters / VMs, I was glad to get this overview of the event. On the other hand, I was hoping to get more juicy bits on the technology side of Rubinius. Since I’m already familiar with the project (from afar), I thought maybe I could come up with another more technical view on Rubinius. Well, let’s say a layman’s technical view on Rubinius.
I’ve been putting off this series of articles for a couple of weeks because I took the opportunity to talk about Rubinius at the last Montreal on Rails. I didn’t want to steal my own punch lines on my blog before the presentation.
In this series of articles I’ll try to present the Rubinius project from a technical point of view. A 10 000 ft technical view, that is. Some of what I say here is in part based on Evan Phoenix’ presentation at RubyConf 2007, so some of it could sound familiar to those who attended or watched the video (highly recommended).
Q: What is Rubinius?
A: A Ruby VM and Runtime written as much as possible in Ruby, and as little as possible in C. The goal is eventually to completely eliminate the C code.
Of course, the first question that comes to mind is: WTF? It’s a perfectly understandable reaction. So in this first article I’ll mostly address the 3 letter question. The next articles will address all the other aspects of this fascinating project.
It’s not a bad idea
It’s a traditional milestone for any language: the ultimate level of dogfooding. Implementing a VM or a runtime for a language with this very language means a lot in terms of the language’s maturity and power.
And this is valid for many other technologies, too. It’s been a very important milestone for Subversion as well some years back, for example. Their exponential growth really took off when they started using their own Subversion server to host their repository.
Some Ruby skeptics might say that Ruby is not fast and mature enough to implement a VM.
Let’s first address the assertion that ‘Ruby is slow’. It’s a sentence that doesn’t really make sense, when you think about it: Ruby is a language. Apart from being dynamic, very little of the language’s characteristics fundamentally limit the performance it can get. Its current performance problems rather come from its default interpreter, MRI.
As for the maturity of the language, I agree that some of its features may not be such great ideas. Current work on alternative implementations has brought some of them to light, and from here they mostly look like features that are not in the right place. If a project depends on ObjectSpace, well maybe a better design could circumvent that need. And tainting might not be the most sound security model. Whatever. The parts that don’t work are as of now slowly being weeded out by the different Ruby implementers, including by Matz himself who works on YARV.
So Ruby is maturing right now. The ultimate level of dogfooding to help that process is to use it to implement itself.
It’s actually a great idea
Who wants to program in Ruby?
If you’re interested in working on VM technology and you love the Ruby language, here are your options*:
Interpreter | LOC (non Ruby) | LOC (Ruby) |
---|---|---|
MRI (Ruby 1.8) | 85 000, C | 0 |
YARV (Ruby 1.9) | 129 000, C | 0 |
JRuby | 115 000, Java | ~ 1 000 |
IronRuby | 48 000, C# | 0 |
Rubinius | 25 000, C | 14 000 |
I finally have a day job writing Ruby (thanks Carl). I’m sorry to tell you that I’m not interested in going back to Java or C# in my spare time if I can help it.
Actually, the case is probably even stronger for my fellow programmers who work with Java or C# during the day, dreaming of coming back to their Ruby-red passionate lover at night. Very few of them will come home and fire up Visual Studio / Eclipse / Netbeans, methinks.
- Actually this is an overstatement: some of these projects are very open to contribution, while others are pretty much closed to outside contribution. More on that later.
** These number are those from Evan’s presentation at the end of 2007, so they’re probably quite outdated. My other option was running sloccount on 5 projects I don’t know closely enough and getting all the numbers wrong :-)
Getting feature-complete with how many lines of code?
Another side effect of using Ruby to implement Rubinius seems to be often overlooked. Rubinius is getting close to feature-complete with under 50 000 LOC. Ruby’s a very expressive language, remember?
The Ruby part is reusable
Here’s what I expect from a feature-complete Ruby interpreter:
Actually, if we look underneath, we see MRI and YARV are implemented that way:
When we look at Rubinius, as mentioned already, we see it’s implemented like that:
So if I want to implement my own revolutionary Ruby interpreter with LOLCODE, I now have the option to implement a core VM and use as much as possible from Rubinius for the rest:
So by reusing Rubinius’ Ruby runtime, 1.0 is gonna come much faster now. If some parts are too slow and for some reason can’t be sped up enough with this approach, I can always get back to the bare metal LOLCODE when comes time to optimize performance. Neat, isn’t it?
But… how is that possible?
Enough about why it’s a great idea. Is it even possible to implement Ruby in Ruby (and end up with something decent)? Actually, yes. It’s been done before for languages with similar characteristics.
Ruby’s a dynamic language. Dynamic languages have the reputation of being slow, right? I can concede that there’s probably an upper bound to how fast a dynamic language can get. And this upper bound is probably lower than that of a statically typed language.
But let’s not forget that Smalltalk and Lisp are dynamic languages as well. There are very fast implementations of both languages. Word on the street is that Smalltalk has been seen running at about half the speed of C. Unfortunately, I have no actual precise reference on that. You should therefore assume that it’s a lie. Just pay attention when you hear the name Strongtalk.
A lot of research has been made in the Smalltalk community in the past 30 years. The Rubinius team is drawing on this research as well as other recent virtual machine research. The VM is loosely based on the Smalltalk-80 architecture as described in the Blue Book.
Still don’t believe me?
Here’s how a recent build of Rubinius compares to MRI.
An overview
Comparison | MRI | Rubinius | Blah blah blah |
---|---|---|---|
Performance winner | 12 / 41 | 28 / 41 | Big improvement over the numbers shown by Evans at RubyConf 2007 |
Errors | 2 | 0 | |
Timeouts | 2 | 2 | It’s a tie! |
The details
These following measurements are in seconds, so lower is better.
Benchmark | MRI | Rubinius |
---|---|---|
bm_app_answer.rb | 0.526643 | 0.687349 |
bm_app_factorial.rb | Error | 0.859864 |
bm_app_fib.rb | 6.112832 | 2.934078 |
bm_app_mandelbrot.rb | 2.185676 | 8.210969 |
bm_app_pentomino.rb | Timeout | Timeout |
bm_app_raise.rb | 2.362519 | 1.898819 |
bm_app_strconcat.rb | 1.523553 | 2.042877 |
bm_app_tak.rb | 8.835661 | 4.003406 |
bm_app_tarai.rb | 7.101123 | 3.922363 |
bm_loop_times.rb | 4.696043 | 6.964689 |
bm_loop_whileloop.rb | 8.90748 | 1.929943 |
bm_loop_whileloop2.rb | 17.83614 | 3.37839 |
bm_so_ackermann.rb | Error | 3.073072 |
bm_so_array.rb | 6.388754 | 5.85562 |
bm_so_concatenate.rb | 1.791939 | 10.598892 |
bm_so_count_words.rb | 0.067646 | 1.985533 |
bm_so_exception.rb | 3.754216 | 2.642568 |
bm_so_lists.rb | 10.856124 | Timeout |
bm_so_matrix.rb | 3.135463 | 3.997002 |
bm_so_nested_loop.rb | 7.52997 | 9.081536 |
bm_so_object.rb | 6.33225 | 4.37211 |
bm_so_random.rb | 1.939179 | 5.510661 |
bm_so_sieve.rb | Timeout | 16.840808 |
bm_vm1_block.rb | 21.596147 | 20.841016 |
bm_vm1_const.rb | 15.186479 | 5.147345 |
bm_vm1_ensure.rb | 14.523337 | 2.636796 |
bm_vm1_length.rb | 18.094924 | 5.00994 |
bm_vm1_rescue.rb | 11.291826 | 2.02582 |
bm_vm1_simplereturn.rb | 21.030805 | 5.55093 |
bm_vm1_swap.rb | 20.567411 | 2.703651 |
bm_vm2_array.rb | 5.013982 | 3.841375 |
bm_vm2_method.rb | 11.07794 | 2.796914 |
bm_vm2_poly_method.rb | 15.352027 | 5.173005 |
bm_vm2_poly_method_ov.rb | 3.980024 | 1.352886 |
bm_vm2_proc.rb | 5.927936 | 4.371145 |
bm_vm2_regexp.rb | 3.763075 | 28.006719 |
bm_vm2_send.rb | 3.868001 | 1.462143 |
bm_vm2_super.rb | 4.582815 | 1.632422 |
bm_vm2_unif1.rb | 3.475184 | 2.325038 |
bm_vm2_zsuper.rb | 5.033829 | 2.503812 |
bm_vm3_thread_create_join.rb | 0.021605 | 2.625211 |
For the more visual among us
(click for full size)
So there’s clearly something there, considering the fact that right now the focus is not really on performance, but rather on implementing features and on correctness.
Of course keep in mind that each one of these benchmark is merely a (contrived) measurement of the performance of a very specific operation, executed a bunch of times. Every real life application will have it’s own performance characteristics. So do not read too much in the following numbers.
Leave that horse alone
I’ll stop beating this dead horse, now. Once you get past the ‘Ruby in Ruby’ part, there’s still a lot of very interesting aspects to the Rubinius project, both technical and non-technical.
Stay tuned for the next installments, in which I’ll cover some of these other aspects: specs for Ruby, in what ways Rubinius is friendly, enabling new use cases for Ruby, threading, replacing C and how Rubinius might eventually help Rails deployment.
Until then, here are few of the ways you can get more information about Rubinius:
- The most recent Rubinius presentation of Rubinius at Mountain West RubyConf, which I haven’t even checked yet.
- The previous presentation, at RubyConf 2007, one of the sources of information for this post.
- Evan’s blog, Rubinius’ original creator and project leader.
- Adam Gardiner’s blog, one of the committers to Rubinius, who recently started explaining the internals of Rubinius (way more technical than this article).
- The Rubinius website, of course.
- To keep the pulse of the project, you can of course hang out on #rubinius on irc.freenode.net. If however you want to catch up on what you may have missed, the conversations are archived online.