This is part two of an ongoing series about Rubinius:

In this shorter second installment, I’ll present the ways in which Rubinius will be friendly to your multiple personalities:

  • you, the programmer;
  • you, the (potential) contributor.

Rubinius is programmer-friendly

Backtraces

The first reason why I consider Rubinius more programmer-friendly is the better backtraces. I’ll show some examples run from each interpreter’s interactive console.

Take the following piece of buggy code. Notice the nil at the end of the array.

ary=['bob', 'mom', nil].inject([]) {|result, element| result << element.to_sym}

With MRI you get the following:

NoMethodError: undefined method `to_sym' for nil:NilClass
        from (irb):5
        from (irb):5:in `inject'
        from (irb):5:in `each'
        from (irb):5:in `inject'
        from (irb):5

Ok, we have basic information on all the method calls that led to the NoMethodError.

Rubinius goes a step further, however:

NoMethodError: No method 'to_sym' on an instance of NilClass.
   from Kernel(NilClass)#to_sym (method_missing_cv) at kernel/core/kernel.rb:606
   from Object#irb_binding {} at (irb):4
   from Enumerable(Array)#inject {} at kernel/core/enumerable.rb:375
   from Array#each at kernel/core/array.rb:573
   from Enumerable(Array)#inject at kernel/core/enumerable.rb:371
   from Object#irb_binding {} at (irb):4

The backtrace tells me in which module each method on the stack was defined!

Q: How awesome is that?

A: Very!

I’m almost looking forward to debugging buggy mixins or method chains. Aren’t you?

These backtraces will definitely come in handy when debugging in situations where a lot of magic is happening behind the scenes.

Bring back the horse!

There’s another interesting detail about the backtraces. Remember what language Rubinius is written in? Heh. The backtraces Rubinius generates go deeper.

Say we have another piece of buggy code:

s = 'ahh'
s[1] = nil

MRI:

TypeError: can't convert nil into String
 from (irb):5:in `[]='
 from (irb):5

Rubinius:

TypeError: Coercion error: nil.to_str => String failed:
(undefined local variable or method `to_str' for nil)
   from Type.coerce_to at kernel/core/kernel.rb:19
   from Kernel(String)#StringValue at kernel/core/kernel.rb:80
   from String#splice! at kernel/core/string.rb:2192
   from String#[]= at kernel/core/string.rb:360
   from Object#irb_binding {} at (irb):8

Not only is the message more descriptive, but as you can see, we can see deeper into the interpreter’s inner workings.

The argument could be made that this could lead to noisier backtraces. On one hand it’s true that adding 5 lines to a 30+ lines backtrace generated in a Rails unit test will rarely help. On the other hand, all this additional information may sometimes help finding subtle bugs (in user code or in Rubinius) or figure out that our use of a feature is not quite correct.

I say the additional information is very welcome. And to help keep the information overload in check, let’s get off our asses and use the quiet_backtrace gem by James Golick and Dan Croak.

The real Rubinius backtraces

It gets even better. The previous examples were only run in irb. So the additionnal information was getting squeezed in IRB’s exception formatting. Say I save both pieces of buggy code to 1.rb and 2.rb. You’ll notice too that I split program 2 in 2.rb and /lib/2.rb (I plan on making it a gem).

Here’s in full colored glory the insults Rubinius will throw back at me:

mat@laptop rubinius $ rbx 1.rb
An exception has occurred:
    No method 'to_sym' on an instance of NilClass. (NoMethodError)Backtrace:
    Kernel(NilClass)#to_sym (method_missing_cv) at kernel/core/kernel.rb:612
                        Object#__script__ {} at 1.rb:1
                 Enumerable(Array)#inject {} at kernel/core/enumerable.rb:375
                                  Array#each at kernel/core/array.rb:573
                    Enumerable(Array)#inject at kernel/core/enumerable.rb:371
                           Object#__script__ at 1.rb:1
                    CompiledMethod#as_script at kernel/core/compiled_method.rb:326
                         Compile.single_load at kernel/core/compile.rb:238
                 Compile.load_from_extension at kernel/core/compile.rb:310
                           Object#__script__ at kernel/loader.rb:190

mat@laptop rubinius $ rbx 2.rb
An exception has occurred:
    Coercion error: nil.to_str => String failed:
(undefined local variable or method `to_str' for nil) (TypeError)
Backtrace:
                 Type.coerce_to at kernel/core/kernel.rb:19
     Kernel(String)#StringValue at kernel/core/kernel.rb:80
                 String#splice! at kernel/core/string.rb:2192
                     String#[]= at kernel/core/string.rb:360
              Object#__script__ at ./lib/2.rb:2
       CompiledMethod#as_script at kernel/core/compiled_method.rb:326
            Compile.single_load at kernel/core/compile.rb:238
        Compile.unified_load {} at kernel/core/compile.rb:149
                     Array#each at kernel/core/array.rb:573
           Compile.unified_load at kernel/core/compile.rb:120
         Kernel(Object)#require at kernel/core/compile.rb:450
              Object#__script__ at 2.rb:1
       CompiledMethod#as_script at kernel/core/compiled_method.rb:326
            Compile.single_load at kernel/core/compile.rb:233
    Compile.load_from_extension at kernel/core/compile.rb:310
              Object#__script__ at kernel/loader.rb:190

Holy f****** sh** Batman! How centering the lines makes a difference!

To the left is the logical location: the class/module and the method name. To the right is just the necessary information about the physical location. Notice the paths. They only contain what you need i.e. the relative path to execution — relative to the VM for Rubinius code, and relative to the execution root of my glorious code.

Don’t tell anyone, but I think I’ll intentionally put in bugs at work. Just to see these in the logs! Too bad our logs don’t support coloring.

Rubinius is contributor-friendly

Commit access

The Rubinius project is friendly for the programmer who uses it. But the project itself is also friendly to potential contributors. They currently have a policy called the free-flowing commit bit. This policy says the following:

Anyone who submits a patch which is accepted is granted commit access.

In case you missed it, here it is again:

Anyone who submits a patch which is accepted is granted commit access.

This is not crazy talk. The patch can be as simple as you want. A correction to documentation or a spec for the behavior of a Ruby class not behaving properly under Rubinius. Any form of submission is admissible: a pastie, a ticket prepended with [PATCH] on lighthouse, or any other traditional way (emails, ransom notes etc.) Of course these methods are only temporary, since you’re going to get commit access afterwards.

The idea in being so liberal with commit access is to put the barrier to contribution as low as possible. This way, when your commit bit is set, you’re going to be tempted to help more. And your next contribution is going to be so much easier to submit.

A masterful adaptation of the timeless ‘gateway drug’ style of recruiting, really. Get them hooked with as little friction as possible. Then let them, uhhh, flourish in the wonderful world they just discovered.

How’s that for being contributor-friendly? This may not last forever, I guess. So hurry up and submit a patch! ;-)

The naysayers might once again intervene to remind us that this isn’t going to scale. Well it’s every open source project’s dream to have too many contributors. The community and the team will adapt in time. The fact that Rubinius is hosted with Git also gives the team much more flexibility to try different committing strategies, or simply gives more power to recover from mistakes from newcomers.

To really have a better understanding of Evan’s vision of the Rubinius community, I highly recommend watching the video of his presentation at MountainWest RubyConf. The presentation’s content is really not the same as the other presentation mentioned in my first article. Both are really worth it.

Conclusion

That’s it for now, folks. This is how much Rubinius will be friendly to you. Delicious stacktraces and a very welcoming project. A project ready to give you commit access if you submit anything good: a spec, some doc, or just a plain ol’ bug fix.

Stay tuned for the next installment, in which I’ll start covering a few mildly technical bits of Rubinius. Unless I change my mind and decide to tackle another aspect of Rubinius.

A few references:

  • The links for the videos are 3 paragraphs up, you lazy reader. No wait, I’m the one being lazy here.
  • How to write a ticket for the Rubinius project.
  • Let’s bug James Golick and Dan Croak so they make sure quiet_backtrace is ready for these deep Rubinius stack traces. Ha! Sorry James :-)

I’m taking a break from the Rubinius for the Layman series. I don’t know why my posts always spiral into multi-thousand words essays :-) Even worse, when I try to write about Rubinius it’s so easy to get into ratholes and start fiddling, poking at and exploring this wonderful beast. All of that instead of writing, of course.

Tonight’s fiddling led me to playing around with my .irbrc file. I guess we’ve all searched for examples of config files for IRB at one time or another. When you spend a lot of time into your interactive console, you naturally end up wanting to tweak it to your liking.

However you’ll start having problems as soon as you start fooling around with the different runtimes. Or when you share your irbrc between different machines. Maybe even on different OSes. Either some gems are unsupported or you haven’t installed them yet (and don’t necessarily care).

So tonight I tried to fix that problem. My solution is only a small method that I define in my irbrc, so I’m not sure it’s worth putting it on github yet :-) Let me know what you think.

Presenting tramp_require

Usage

When my irbrc requires something just so I don’t need to do it manually:

tramp_require 'pp' #=> true/false (same return value as a normal require)

Outcome if the gem is not installed:

** Unable to require 'wirble'
--> LoadError: Did not find file to load: wirble

And you IRB loads without a problem (just without the gem pre-loaded).

When I require something I actually use in my .irbrc:

tramp_require('wirble') do
  Wirble.init(:skip_prompt=>true)
  Wirble.colorize
end

If the gem is loaded successfully, the block is executed.

If the gem isn’t loaded successfully, the block’s not executed and the same warning message is shown.

Note that the user code passed in the block is not being rescued: if it your code fails, it’s your problem :-)

Implementation

Here I’ll paste 2 equivalent implementations. The first one is a clean and understandable version (also on pastie):

def tramp_require(what, &block)
  loaded, require_result = false, nil 

  begin
    require_result = require what
    loaded = true 

  rescue Exception => ex
    puts "** Unable to require '#{what}'"
    puts "--> #{ex.class}: #{ex.message}"
  end 

  yield if loaded and block_given? 

  require_result
end

This second version is the one I actually use, if I set the debug variable to false the result is the same as the previous implementation. If I set it to true, I get much more information, including the full backtrace of the exception (also on pastie):

$debug_irbrc=false 

def tramp_require(what, &block)
  loaded, require_result = false, nil 

  begin
    puts('', "requiring #{what}") if $debug_irbrc
    require_result = require what
    loaded = true
    puts "successfully required #{what}" if $debug_irbrc 

  rescue Exception => ex
    puts "** Unable to require '#{what}'"
    exception_details = "#{ex.class}: #{ex.message}"
    if $debug_irbrc
      ex.backtrace.reverse.each{|l| exception_details < < "\n   #{l}"}
    else
      exception_details.insert(0, "--> ")
    end
    puts exception_details
  end 

  if loaded and block_given?
    puts "executing block for #{what}" if $debug_irbrc
    yield
  end 

  require_result
end

Feel free to use these snippets as you see fit :-)

The obligatory full paste of my irbrc is available on pastie, for the curious.

One word of warning, however. I’m guessing some of you will rename tramp_require to something more boring. Just don’t rename it to irb_require. IRB already defines a method named this way.

This is part one of an ongoing series about Rubinius:

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:

ruby_interpreter_expectations.png

Actually, if we look underneath, we see MRI and YARV are implemented that way:

ruby_interpreter_mri.png

When we look at Rubinius, as mentioned already, we see it’s implemented like that:

ruby_interpreter_rubinius.png

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:

ruby_interpreter_lolcode.png

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)

YARV microbenchmarks - Rubinius vs MRI

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: