Mar 1 2008

MacRuby

MacRuby “is a version of Ruby that runs on top of Objective-C. More precisely, MacRuby is currently a port of the Ruby 1.9 implementation for the Objective-C runtime and garbage collector. The rationale behind this effort is to solve in a very efficient way all the bridging problems RubyCocoa, the Ruby bridge to the Objective-C runtime, has to work around.” (It’s still in development, and not ready for prime-time use yet.)

This is exciting news. Now, Mac OS X already has excellent Ruby support, with a complete Ruby 1.8 and Rails stack (including extras like Mongrel and Capistrano) included in the box. There’s also a glue library called RubyCocoa that bridges between the Ruby and Objective-C languages, making it easy to write all or part of a Cocoa app in Ruby (or to call useful Obj-C frameworks from a Ruby app.)

So why MacRuby? The big deal is performance. By re-implementing the Ruby object model on top of the Objective-C runtime — so all Ruby objects are bona fide Objective-C objects and vice versa — it will eliminate serious amounts of overhead:

  • Parameter marshalling whenever a Ruby method has to call into Objective-C or vice versa
  • Bookkeeping data structures that map between Ruby and Objective-C objects and classes
  • Incompatible threading models
  • Multiple garbage collectors

It also takes advantage of the major speed boost of the new bytecode interpreter in Ruby 1.9.

From my experiences working on Apple’s Java runtime, I know that bridging to and from native code is a major performance issue. The amount of overhead for a single inter-language call may not seem like much (a few microseconds), but it’s one of those “death by a thousand cuts” effects, because in an application that uses native libraries it happens so often.

(For example, one of the ways I was able to speed up AWT graphics performance by about 5x in MRJ 2.1 was to pipeline the calls, so instead of each java.awt.Graphics method calling directly into the native QuickDraw API, it just appended the parameters to a display-list array, which periodically got flushed through a single call to a native C++ method that walked the list and made all the QuickDraw calls. I didn’t initially believe this would be a big win, but Steve Zellers wrote a prototype version that proved it.)

Another nice feature of MacRuby is that it slightly extends Ruby’s method-call syntax to allow Objective-C style interleaved keywords and arguments. So when calling an Objective-C -setFirstName:lastName: method, instead of writing
    person.setFirstName_lastName(first,last)
you write
    person.setFirstName first, lastName: last
or alternatively
    person.setFirstName first, :lastName => last
This may offend Ruby purists, and it’s really just syntactic sugar, but by eliminating conceptual bridging overhead, I think it’ll improve programmer performance, which is also an important optimization.

Finally, another good thing that I didn’t realize right away is that it’s an official Apple open-source project. So not only does that make it very likely that a finished version of MacRuby will be built into 10.6; it also means that, if it’s necessary to tweak the Objective-C runtime to make MacRuby work better, that’s likely to happen too. The people working on runtimes and scripting languages at Apple are really smart, and I’m glad to see they’re not resting on their laurels after all they achieved in 10.5.


8 Responses to “MacRuby”

  • Michael Tsai - Blog - MacRuby Says:

    […] MacRuby sounds very cool (via Jens Alfke): In MacRuby, all Ruby classes and objects are actually Objective-C classes and objects. There is no need to create costly proxies, convert objects and cache instances. A Ruby object can be toll-free casted at the C level as an Objective-C object, and the Ruby VM can also handle Objective-C objects without conversion. […]

  • Chris Says:

    Another nice feature of MacRuby is that it slightly extends Ruby’s method-call syntax to allow Objective-C style interleaved keywords and arguments.

    Actually, it does not. That syntax is already available in plain Ruby. The ‘alternative’ syntax is available in Ruby 1.8, while Ruby 1.9 supports the more Objective-C like syntax. Do this in plain-jane irb with Tiger or Leopard (hope this gets formatted properly):


    ?> def thing(one, two)
    >> puts one
    >> puts two.inspect
    >> end
    => nil
    >> thing('equation', 'hot' => 'cold', 'love' => 'hate')
    equation
    {"love"=>"hate", "hot"=>"cold"}
    => nil

  • Chris Says:

    In other words, it’s a handy bit of Ruby’s famous syntactic sugar: trailing method call arguments in hash definition format are combined into a hash and passed as such to the method. Ruby 1.9 adds {key: value} as acceptable hash definition syntax.

  • Jens Alfke Says:

    Chris — it’s not quite the same, semantically. In Obj-C selectors the order of keywords is significant (-this:that: is not the same as -that:this:) and a keyword can be repeated (-performSelector:withObject:withObject:). If Ruby converts such arguments into a hash, both of those attributes would be lost.

  • Chris Ryland Says:

    Jens—

    It’s a small point, but even using the Obj-C runtime completely in all other respects can’t eliminate marshalling overhead, unless you’re also generating machine code in the same way the gcc/clang compilers are doing, and then you’re not in the interpreted (Ruby VM) world any more.

    I’m thinking about this, as I’m working on a similar project, and I don’t see any way around this (short of Obj-C-style code generation).

  • Chris Ryland Says:

    Also, I don’t see how you can base a Ruby (or Python or any other language) implementation on the Obj-C runtime, and remain true to the original language semantics at the lower levels (e.g., metaclasses, etc.). The result will be neither fish nor fowl, though it still might be useful, if it’s Ruby-like.

  • Jens Alfke Says:

    You’re right that there will still be some marshaling going on, but objects are the most expensive things to marshal, due to the need to proxy or copy, so this will definitely help.

    I’m sure metaclasses will be tricky. But the Obj-C runtime gives you a fair amount of control over method resolution, so I suspect it’ll be possible somehow. It sounds as though they want to do a faithful implementation of Ruby, not something just Ruby-like.

  • Laurent Sansonetti Says:

    If Ruby converts such arguments into a hash, both of those attributes would be lost.

    Definitely, and this is why RubyCocoa wasn’t able to use this syntax for Objective-C selector messaging. In 1.9, hash keys are ordered in the same way they are provided, but this still doesn’t solve the multiple keys problem. MacRuby has a modified copy of the Ruby parser that is now able to reconstruct the complete selector (internally it doesn’t use a Hash anymore but a list of key/pair elements. It still sends a Hash if the receiver doesn’t respond to the selector, to preserve compatibility.

    We can’t re-implement the entire Ruby semantics with the Objective-C runtime. For example, it doesn’t support instance variables to be added at runtime into a class, or classes to be nested. So, Ruby classes in MacRuby are not 100% Objective-C classes, because we still allocate some extra data structure to store the Ruby bits. But, Ruby objects are Objective-C objects, and Ruby methods are available in the Objective-C runtime.

    Regarding the marshaling overhead, a MacRuby message will be of course slower than sending the same message in straight Objective-C. But as Jens says, since we don’t have to convert the arguments and the return value, it’s much more faster than a bridging solution. I also plan to unify the Ruby exception model with the same one as Objective-C, which has a costless “try” operation on 64-bit.

    Will it be fast? I think it should be fast enough :)

Leave a Reply