Compiling Ruby: From Text to Bytecode(engineyard.com)
engineyard.com
Compiling Ruby: From Text to Bytecode
http://www.engineyard.com/blog/2009/the-anatomy-of-a-ruby-jit-compile/
2 comments
It's a combination of both, actually. From what I understand, Matz is more of a dreams guy than an implementations guy. He made MRI work, but never really intended it to be fast or efficient. This is why 1.9 is a totally different compiler; Matz can go back to designing, and others can worry about implementations
There's also quite a bit of Ruby that's non-trivial to compile. For instance, think about method_missing. If a message gets sent to an object, a naive implementation would have to check every parent class up the inheretance chain, and then do it all over again to see if a method_missing is defined, too. That's quite a bit of time. Also, things like open classes make things like caching hard, as well.
None of these problems are insurmountable, but they take some work and some effort. Luckily, Ruby has those people now.
There's also quite a bit of Ruby that's non-trivial to compile. For instance, think about method_missing. If a message gets sent to an object, a naive implementation would have to check every parent class up the inheretance chain, and then do it all over again to see if a method_missing is defined, too. That's quite a bit of time. Also, things like open classes make things like caching hard, as well.
None of these problems are insurmountable, but they take some work and some effort. Luckily, Ruby has those people now.
This is also why things like the double-inclusion problem are still live issues.
http://eigenclass.org/hiki/The+double+inclusion+problem
One problem that Ruby does need to deal with is when implementation problems become institutionalised as language specifications.
http://blog.jcoglan.com/2009/06/06/when-bugs-become-features...
http://eigenclass.org/hiki/The+double+inclusion+problem
One problem that Ruby does need to deal with is when implementation problems become institutionalised as language specifications.
http://blog.jcoglan.com/2009/06/06/when-bugs-become-features...
Thanks! You've answered my question completely(and then some).
It is worth noting that the performance of Ruby varies even between the various implementations available for production use.
So, if I had to attribute 'slowness', I'd say it's because of the implementation (compiler/interpreter) instead of the language design
"Slowness" is subjective, but I think this answers your question.
So, if I had to attribute 'slowness', I'd say it's because of the implementation (compiler/interpreter) instead of the language design
"Slowness" is subjective, but I think this answers your question.
The big gain in speed will come from optimizing away the "meta_send_op_plus" operation. That has to do a hash lookup, which is much slower than an array index.
Ruby should be taking the same route (PIC) that the Javascript VMs are. Perhaps this is planned for one of the Ruby VMs but I haven't heard anything along these lines yet.
Ruby should be taking the same route (PIC) that the Javascript VMs are. Perhaps this is planned for one of the Ruby VMs but I haven't heard anything along these lines yet.
I'm working on an ahead of time compiler for Ruby as a sort of bastard child of my "Writing a compiler in Ruby" article series, and yes, PICs will be one of many essential steps to get decent performance out of Ruby (far down the list though, as I'm still far from having something that works :) ). I believe the Rubinius guys have been looking at PICs or similar methods as well.
You can actually do better in many cases, by using C++ style vtables which only requires an array lookup. The are two caveats with vtables for Ruby: Since all objects inherit from Object, all classes need a vtable large enough to hold all methods known at compile time, and secondly you need a fallback mechanism to handle method names not known at compile time. The size overhead is actually not necessarily so bad for typical apps - my compiler itself would have a vtable overhead of about 40K ("vasted" vtable slots) but the hash approach also have (smaller) overheads so the total amount of memory wasted is even lower.
You also need to handle re-definition of methods at compile time, but that's a simple matter of propagating the vtable change down the inheritance hierarchy until you reach the bottom or a subclass that has already overloaded the method (Prof. Michael Franz, who's one of the guys behind "trace trees" which is also gaining traction in the JS world wrote a report on using that method to add runtime interface - or protocol - changes to Oberon back in 93 I think). It makes dynamic method redefinition slower, but that's far more rare than dynamic method calls.
There are other parts that are trickier. You can redefine Fixnum for example, which means that you need to either do lots of analysis and have a fallback prepared, use type tagging (like MRI) or make integers actual objects. The alternatives are either difficult (analysis+fallback), bloats and slows your code (type tags and objects) and/or causes extra memory usage and pressure on your FC (making integers real objects).
You also have the "require" problem. A common Ruby idiom is to loop over a directory and require all files in it. Do you require it at compile time, or at runtime? Granted, that part is easier for a JIT (though even an ahead of time compiler for Ruby will need to include either a small VM or a JIT to handle eval and completely dynamic require/load's)
Not to mention the fun little issue of code executing in a class definition, meaning you don't actually even know what methods a class has without potentially executing code. Heck, for a really pathological case, more than one Ruby ORM establishes a database connection and executes queries in the class definition in order to add methods to the class then and there.
All of this makes Ruby really painful to compile efficiently since you know so little about the program without actually executing it.
You can actually do better in many cases, by using C++ style vtables which only requires an array lookup. The are two caveats with vtables for Ruby: Since all objects inherit from Object, all classes need a vtable large enough to hold all methods known at compile time, and secondly you need a fallback mechanism to handle method names not known at compile time. The size overhead is actually not necessarily so bad for typical apps - my compiler itself would have a vtable overhead of about 40K ("vasted" vtable slots) but the hash approach also have (smaller) overheads so the total amount of memory wasted is even lower.
You also need to handle re-definition of methods at compile time, but that's a simple matter of propagating the vtable change down the inheritance hierarchy until you reach the bottom or a subclass that has already overloaded the method (Prof. Michael Franz, who's one of the guys behind "trace trees" which is also gaining traction in the JS world wrote a report on using that method to add runtime interface - or protocol - changes to Oberon back in 93 I think). It makes dynamic method redefinition slower, but that's far more rare than dynamic method calls.
There are other parts that are trickier. You can redefine Fixnum for example, which means that you need to either do lots of analysis and have a fallback prepared, use type tagging (like MRI) or make integers actual objects. The alternatives are either difficult (analysis+fallback), bloats and slows your code (type tags and objects) and/or causes extra memory usage and pressure on your FC (making integers real objects).
You also have the "require" problem. A common Ruby idiom is to loop over a directory and require all files in it. Do you require it at compile time, or at runtime? Granted, that part is easier for a JIT (though even an ahead of time compiler for Ruby will need to include either a small VM or a JIT to handle eval and completely dynamic require/load's)
Not to mention the fun little issue of code executing in a class definition, meaning you don't actually even know what methods a class has without potentially executing code. Heck, for a really pathological case, more than one Ruby ORM establishes a database connection and executes queries in the class definition in order to add methods to the class then and there.
All of this makes Ruby really painful to compile efficiently since you know so little about the program without actually executing it.
I just wanted to say thanks for writing all of those "Write a compiler in Ruby" articles. They've been really interesting and informative. For a little while, I was following along, but targeting x86_64 rather than x86 so that it didn't feel like I was just copy/pasting everything that you did...
That's great... More parts will be coming - other parts of life has just been interfering :) If you ever feel like trying your hands at making any changes, it's on all on Github.
At the moment I'm just focusing on getting attr_reader and friends working. Sounds simple, but since I wanted to write attr_reader and attr_writer mainly in Ruby it's a rabbit hole that required me to get basic parts of Symbol and String working, as well as define_method, fixing various method dispatch issues, fixing string interpolation and more; those changes will make up the bulk of the content of the next couple of parts I write. Once all those pieces are in place a lot of the rest of what's needed to compile a fairly decent subset of Ruby is pretty simple (though lots of gruntwork). With some luck (and enough spare time) I hope to get the compiler self-hosted by the end of the year.
At the moment I'm just focusing on getting attr_reader and friends working. Sounds simple, but since I wanted to write attr_reader and attr_writer mainly in Ruby it's a rabbit hole that required me to get basic parts of Symbol and String working, as well as define_method, fixing various method dispatch issues, fixing string interpolation and more; those changes will make up the bulk of the content of the next couple of parts I write. Once all those pieces are in place a lot of the rest of what's needed to compile a fairly decent subset of Ruby is pretty simple (though lots of gruntwork). With some luck (and enough spare time) I hope to get the compiler self-hosted by the end of the year.
Stupid question, but in this context what does "PIC" stand for? The best I came up with was "Partially Inlined Code."
Polymorphic inline caching. This article might give a bit more insight into how modern JavaScript virtual machines like V8 and SFX use it.
http://techon.nikkeibp.co.jp/article/HONSHI/20090106/163620/
http://techon.nikkeibp.co.jp/article/HONSHI/20090106/163620/
Both. Ruby is hard to compile or interpret efficiently, but there's been plenty of research into ways of handling the same type of problems that Ruby has - look at Smalltalk and Self for example.
Ruby can be made a lot faster than current implementations, but the reasons it hasn't been made faster a long time ago is to a large extent down to the language design.
Now, how many of those decisions you could've made differently in order to get more performance without at the same time losing what makes Ruby so great to work with is another question...
Ruby can be made a lot faster than current implementations, but the reasons it hasn't been made faster a long time ago is to a large extent down to the language design.
Now, how many of those decisions you could've made differently in order to get more performance without at the same time losing what makes Ruby so great to work with is another question...
I'm suprised that no one has mentioned JRuby in this context put here is a great post related to your question:
http://blog.headius.com/2009/04/how-jruby-makes-ruby-fast.ht...
This post shows how if you turn off certain Ruby language features you can make it much faster. Then he shows how his Ruby-like but static language is much faster.
http://blog.headius.com/2009/04/how-jruby-makes-ruby-fast.ht...
This post shows how if you turn off certain Ruby language features you can make it much faster. Then he shows how his Ruby-like but static language is much faster.
Wow, Rubinius that's certainly a blast from the past. Its great that the project is still ongoing.
I especially like the link at the bottom of the post to the Rubinius book tour http://bit.ly/1wVU4 aka what you can read to fully understand or design a Rubinius.
Faster speed is always good. However, my interest in Rubinius is more of a learning thing. Its really cool to be able to check the implementation of class 'Class' http://bit.ly/fCyIw when you need to or fully understand how some underlying algorithms implemented in the language are put together in just plain old Ruby code (not C).
I especially like the link at the bottom of the post to the Rubinius book tour http://bit.ly/1wVU4 aka what you can read to fully understand or design a Rubinius.
Faster speed is always good. However, my interest in Rubinius is more of a learning thing. Its really cool to be able to check the implementation of class 'Class' http://bit.ly/fCyIw when you need to or fully understand how some underlying algorithms implemented in the language are put together in just plain old Ruby code (not C).
Here is a question, is the 'slowness' of ruby because of its compiler/interpreter or is it because of its language design?