CS Nerds Anonymous
I’m also holding a last minute addition to the RailsConf schedule, CS Nerds Anonymous.
I’ll be chairing, getting the conversation started, basically being the ring leader. My hope is that people show up with fun topics to discuss, and we’ll take it from there. There is a good chance that we’ll have some lightning talks too. Again, doesn’t have to be Rails or even Ruby specific. Just fun topics that you think your fellow CS nerds would enjoy!
So wake up early on Sunday and come hang out! Bring your opinions on everything from optional type annotations in Ruby to Erlang monads!
Code Drive at RailsConf
A heads up that I’ll be participating in the RailsConf Community CodeDrive, hacking on Rubinius
Come hang out with me and other project hackers and share the love!
Bring your questions, quandaries, criticisms, and code!
See you Thursday morning at 10am!
Simple VM JIT with LLVM
I’ve been investigating using LLVM for Rubinius, so I’ve been doing some very small scale experiments. I typically do this on most projects, to get a mental handle on the problem.
In doing this, I’ve written a very tiny VM to play with how LLVM handles it. Here is the breakdown of the entire VM:
- Only operates on ints.
- Uses numbered registers for operations.
- 3 Instructions:
- set(reg, val) Set register number reg to integer value val
- add(result, reg, val) Add register reg to val and put the result in result
- show(reg) Print out the contents of register reg
Pretty trivial. Not turing complete because there is no branching. But it’s simple enough to explore how to handle bytecode in LLVM.
Sample Program
So, I’ve written a sample program, encoded directly as bytecode:
[ 0, 0, 3,
1, 0, 0, 4,
2, 0 ]
This is:
- Set register 0 to 3
- Add register 0 to 4 and put the result in register 0
- Show register 0
Again, totally trivial. Should just output 7.
C Switch
So, the VM Design 101 way is to build a C switch statement to interpret each bytecode and perform it’s operations. It would look like this, given that int ops[] contains the above integer sequence for the program:
void add(int* ops, int* registers) {
registers[ops[1]] = registers[ops[2]] + ops[3];
}
void set(int* ops, int* registers) {
registers[ops[1]] = ops[2];
}
void show(int* ops, int* registers) {
printf("=> %d\n", registers[ops[1]]);
}
void run(int* ops, int* registers) {
switch(*ops) {
case 0:
set(ops, registers);
ops += 3;
break;
case 1:
add(ops, registers);
ops += 4;
break;
case 2:
show(ops, registers);
return;
}
}
Very easy. We increments ops to move forward, using ops as a pointer to the current instruction. This is how every VM starts. But this code is very slow when compared to machine code because it obscures the execution flow from the CPU. And besides, this doesn’t use LLVM, the whole point of this post.
A static result…
As we look at the code that was run, we see that the program is set, add, then show. Lets pretend for a sec that given the above functions, we want to perform the same operation, we’d write:
void my_program() {
int registers[2] = {0, 0};
int program[10] = [ 0, 0, 3,
1, 0, 0, 4,
2, 0 ]
int* ops = (int*)program;
set(ops, registers);
ops += 3;
add(ops, registers);
ops += 4;
show(ops, registers);
ops += 2;
}
So, my_program would perform the same operations as your bytecode above, and considerable faster because we avoid all the overhead the switch statement.
Combining the 2
If you look at the bytecode, than the at the hand written C, we can see that there there is a programmatic way to go from our array of numbers to the C code.
A common approach that people have taken in the past to actually write a C code emitter, that would parse the integers once, spit out a .c file, which you’d compile, then run. This works ok for some situations, but it doesn’t allow for any kind of dynamic abilities to run code. And besides, it doesn’t use LLVM.
The idea is to write a function thats takes as input the array of numbers and dynamically builds the equivalent of the above C function. And thats where LLVM comes in.
Part 1: importing the functions
A key part to this scheme is to have the add/set/show functions we defined above available to LLVM. Doing so lets us use our normal tools to write the each instruction as a small operation that can easily be tested. So, given that we have put those 3 functions into ops.c, we run:
llvm-gcc -emit-llvm -O3 -c ops.c, which generates ops.o as a LLVM bitcode file. Using llvm-dis < ops.o we see it contains:
@.str = internal constant [7 x i8] c"=> %dA0" ; [#uses=1]
define void @add(i32* %ops, i32* %registers) nounwind {
entry:
%tmp1 = getelementptr i32* %ops, i32 1 ; [#uses=1]
%tmp2 = load i32* %tmp1, align 4 ; [#uses=1]
%tmp4 = getelementptr i32* %ops, i32 2 ; [#uses=1]
%tmp5 = load i32* %tmp4, align 4 ; [#uses=1]
%tmp7 = getelementptr i32* %registers, i32 %tmp5 ; [#uses=1]
%tmp8 = load i32* %tmp7, align 4 ; [#uses=1]
%tmp10 = getelementptr i32* %ops, i32 3 ; [#uses=1]
%tmp11 = load i32* %tmp10, align 4 ; [#uses=1]
%tmp12 = add i32 %tmp11, %tmp8 ; [#uses=1]
%tmp14 = getelementptr i32* %registers, i32 %tmp2 ; [#uses=1]
store i32 %tmp12, i32* %tmp14, align 4
ret void
}
define void @set(i32* %ops, i32* %registers) nounwind {
entry:
%tmp1 = getelementptr i32* %ops, i32 1 ; [#uses=1]
%tmp2 = load i32* %tmp1, align 4 ; [#uses=1]
%tmp4 = getelementptr i32* %ops, i32 2 ; [#uses=1]
%tmp5 = load i32* %tmp4, align 4 ; [#uses=1]
%tmp7 = getelementptr i32* %registers, i32 %tmp2 ; [#uses=1]
store i32 %tmp5, i32* %tmp7, align 4
ret void
}
declare i32 @printf(i8*, ...) nounwind
define void @show(i32* %ops, i32* %registers) nounwind {
entry:
%tmp1 = getelementptr i32* %ops, i32 1 ; [#uses=1]
%tmp2 = load i32* %tmp1, align 4 ; [#uses=1]
%tmp4 = getelementptr i32* %registers, i32 %tmp2 ; [#uses=1]
%tmp5 = load i32* %tmp4, align 4 ; [#uses=1]
%tmp7 = tail call i32 (i8*, ...)* @printf( i8* getelementptr ([7 x i8]* @.str, i32 0, i32 0), i32 %tmp5 ) nounwind ; [#uses=0]
ret void
}
Now we have our functions ready for easy importing.
Phase 2: LLVM C++ API
When I did this experiment, I decided that the function that, given an array of ints, would drive the LLVM, wasn’t necessary. After all, all I was concerned was if the output of that process would actually work. So instead, I hand wrote a function that uses the LLVM C++ api the same way it would be if this were dynamic:
Function* create(Module** out) {
std::string error;
Module* jit;
// Load in the bitcode file containing the functions for each
// bytecode operation.
if(MemoryBuffer* buffer = MemoryBuffer::getFile("ops.o", &error)) {
jit = ParseBitcodeFile(buffer, &error);
delete buffer;
}
// Pull out references to them.
Function* set = jit->getFunction(std::string("set"));
Function* add = jit->getFunction(std::string("add"));
Function* show = jit->getFunction(std::string("show"));
// Now, begin building our new function, which calls the
// above functions.
Function* body = cast<Function>(jit->getOrInsertFunction("body",
Type::VoidTy,
PointerType::getUnqual(Type::Int32Ty),
PointerType::getUnqual(Type::Int32Ty), (Type*)0));
// Our function will be passed the ops pointer and the
// registers pointer, just like before.
Function::arg_iterator args = body->arg_begin();
Value* ops = args++;
ops->setName("ops");
Value* registers = args++;
registers->setName("registers");
BasicBlock *bb = BasicBlock::Create("entry", body);
// Set up our arguments to be passed to set.
std::vector<Value*> params;
params.push_back(ops);
params.push_back(registers);
// Call out to set, passing ops and registers down
CallInst* call = CallInst::Create(set, params.begin(), params.end(), "", bb);
ConstantInt* const_3 = ConstantInt::get(APInt(32, "3", 10));
ConstantInt* const_4 = ConstantInt::get(APInt(32, "4", 10));
// add 3 to the ops pointer.
GetElementPtrInst* ptr1 = GetElementPtrInst::Create(ops, const_3, "tmp3", bb);
// Setup and call add, notice we pass down the updated ops pointer
// rather than the original, so that we've moved down.
std::vector<Value*> params2;
params2.push_back(ptr1);
params2.push_back(registers);
CallInst* call2 = CallInst::Create(add, params2.begin(), params2.end(), "", bb);
// Push the ops pointer down another 4.
GetElementPtrInst* ptr2 = GetElementPtrInst::Create(ops, const_4, "tmp3", bb);
// Setup and call show.
std::vector<Value*> params3;
params3.push_back(ptr2);
params3.push_back(registers);
CallInst* call3 = CallInst::Create(show, params3.begin(), params3.end(), "", bb);
// And we're done!
ReturnInst::Create(bb);
*out = jit;
return body;
}
Now, lets write a function that calls create() and executes the result:
int main() {
// The registers.
int registers[2] = {0, 0};
// Our program.
int program[20] = {0, 0, 3,
1, 0, 0, 4,
2, 0};
int* ops = (int*)program;
// Create our function and give us the Module and Function back.
Module* jit;
Function* func = create(&jit);
// Add in optimizations. These were taken from a list that 'opt', LLVMs optimization tool, uses.
PassManager p;
/* Comment out optimize
p.add(new TargetData(jit));
p.add(createVerifierPass());
p.add(createLowerSetJmpPass());
p.add(createRaiseAllocationsPass());
p.add(createCFGSimplificationPass());
p.add(createPromoteMemoryToRegisterPass());
p.add(createGlobalOptimizerPass());
p.add(createGlobalDCEPass());
p.add(createFunctionInliningPass());
*/
// Run these optimizations on our Module
p.run(*jit);
// Setup for JIT
ExistingModuleProvider* mp = new ExistingModuleProvider(jit);
ExecutionEngine* engine = ExecutionEngine::create(mp);
// Show us what we've created!
std::cout << "Created\n" << *jit;
// Have our function JIT'd into machine code and return. We cast it to a particular C function pointer signature so we can call in nicely.
void (*fp)(int*, int*) = (void (*)(int*, int*))engine->getPointerToFunction(func);
// Call what we've created!
fp(ops, registers);
}
We drive our create() function and then execute the result. As you can see, we’ve commented out all optimizations as a first try. The output from running this is:
<snip same LLVM as before>
define void @body(i32* %ops, i32* %registers) {
entry:
call void @set( i32* %ops, i32* %registers )
%tmp3 = getelementptr i32* %ops, i32 3 ; [#uses=1]
call void @add( i32* %tmp3, i32* %registers )
%tmp31 = getelementptr i32* %ops, i32 4 ; [#uses=1]
call void @show( i32* %tmp31, i32* %registers )
ret void
}
=> 7
Hey! Look at that! It runs! And we can see what it ran. We see it perform the calls to our functions that implement each opcode. Going back, it would be easily to write a function that dynamically drivers the LLVM C++ API to generate this code, it’s simply one call per bytecode, mapped directly.
Even if that were all that LLVM let us do, it would be worth it, but…
Wait, there’s more!
Before, we ran without optimizations to keep the output simple, lets see what happens we turn them on:
define void @body(i32* %ops, i32* %registers) {
entry:
%tmp1.i = getelementptr i32* %ops, i32 1 ; [#uses=1]
%tmp2.i = load i32* %tmp1.i, align 4 ; [#uses=1]
%tmp4.i = getelementptr i32* %ops, i32 2 ; [#uses=1]
%tmp5.i = load i32* %tmp4.i, align 4 ; [#uses=1]
%tmp7.i = getelementptr i32* %registers, i32 %tmp2.i ; [#uses=1]
store i32 %tmp5.i, i32* %tmp7.i, align 4
%tmp3 = getelementptr i32* %ops, i32 3 ; [#uses=3]
%tmp1.i7 = getelementptr i32* %tmp3, i32 1 ; [#uses=1]
%tmp2.i8 = load i32* %tmp1.i7, align 4 ; [#uses=1]
%tmp4.i9 = getelementptr i32* %tmp3, i32 2 ; [#uses=1]
%tmp5.i10 = load i32* %tmp4.i9, align 4 ; [#uses=1]
%tmp7.i11 = getelementptr i32* %registers, i32 %tmp5.i10 ; [#uses=1]
%tmp8.i = load i32* %tmp7.i11, align 4 ; [#uses=1]
%tmp10.i = getelementptr i32* %tmp3, i32 3 ; [#uses=1]
%tmp11.i = load i32* %tmp10.i, align 4 ; [#uses=1]
%tmp12.i = add i32 %tmp11.i, %tmp8.i ; [#uses=1]
%tmp14.i = getelementptr i32* %registers, i32 %tmp2.i8 ; [#uses=1]
store i32 %tmp12.i, i32* %tmp14.i, align 4
%tmp31 = getelementptr i32* %ops, i32 4 ; [#uses=1]
%tmp1.i2 = getelementptr i32* %tmp31, i32 1 ; [#uses=1]
%tmp2.i3 = load i32* %tmp1.i2, align 4 ; [#uses=1]
%tmp4.i4 = getelementptr i32* %registers, i32 %tmp2.i3 ; [#uses=1]
%tmp5.i5 = load i32* %tmp4.i4, align 4 ; [#uses=1]
%tmp7.i6 = call i32 (i8*, ...)* @printf( i8* getelementptr ([7 x i8]* @.str, i32 0, i32 0), i32 %tmp5.i5 ) nounwind ; [#uses=0]
ret void
}
=> 7
WOW! Now we’re cooking with hot flaming nuclear power! LLVM has the extra mile and rather than just calling our functions that implement each instruction, it’s inlined all that code directly into our dynamically generated function! This means A LOT of additional overhead is discarded because, since our functions themselves did simple operations like store into memory or add 2 numbers, that code is now run a lot more quickly.
It’s commonly known that inlining can dramatically improve performance because it eliminates the over head of function calls (spilling and reload registers, stack frames, etc). And LLVM has just given us a powerful form of that for free.
This doesn’t allow for inlining across things like the kind of method call that Ruby does, but it puts us on the track to being able to feed LLVM enough information to do just that.
LLVM is an amazing piece of software. I can’t wait to start using it more.
Rails on Rubinius
We hit a major milestone tonight. As most people know, we’ve been working to run Rails on Rubinius by RailsConf to have something to show off, even if it’s pretty slow.
Well, I’m super proud to say that tonight, rails served up both static and dynamic pages under Rubinius. Previous to tonight, we’d been blocked just trying to get Rails to even load. I decided to just try loading it up and bang on it enough to get it up and going.
In a scary way, it didn’t take very much code. Which meant we were very close already.
It’s pretty late, so I’m going to keep this short. Big thanks to everyone who’s contributed to Rubinius and had faith in us. Enormous thanks to Engine Yard, without whom I don’t know if we’d been able to reach this amazing height.
More updates to come…
Welcome to the Club
I’ve like to formally welcome the maglev development team over at Gemstone to the Ruby environment club.
For those of you that haven’t yet heard of maglev, it’s a brand new Ruby VM being developed by the folks over at Gemstone. Gemstone is the makers of probably the most advanced object-oriented database used today, and have traditionally been a Smalltalk shop till recently.
With the tide rising on Ruby, I’m happy to see another player enter the field. This only means that Ruby is continuing to mature and see that the community is healthy.
I was personally excited to read an interview with Bob Walker and Avi Bryant concerning maglev, because Rubinius is mentioned more than a few times. They’re looking at Rubinius for a couple of reasons. For one, the RubySpec suite we’ve developing and are about to spin off. The more people that we see using the suite and depending on it, the more mature it will become. Not having a spec for Ruby is commonly touted as a reason that it’s a toy, immature language, and anything we can do to dispel that thinking is good for the community.
The other reason that I’m excited about maglev is that they’re taking a very similar approach to the problem of building a Ruby environment. Like Rubinius, the VM is minimal and most of the kernel is implemented in Ruby.
My hope is that the kernel of Rubinius can be refactored and developed to be generic enough for other environments to use. While I know little about maglev’s current environment, they’re a natural build off the work in the Rubinius kernel. I’d hate to see people develop the code functionality of a ruby environment yet again (I count 5 code bases to this effect currently: MRI, JRuby, Ruby.NET, IronRuby, and Rubinius).
Being able to use a generic Ruby kernel is not unique to a smalltalk style VM. With some luck, it could be used by the folks in other environments as well. In my eyes, this is a big win for everyone. For one, this would mean a common code base that consists of the primary Ruby functionality, and thus would mean a vastly reduced worry of fragmentation. Plus it would alleviate the need for this code to be written again, letting future environment developers focus on taking Ruby to the next level in terms of platform integration, performance, etc.
Rubinius Retort
By now, a good deal of you have read Charles breakdown of Ruby implementations.
If you haven’t please go read at least the Rubinius section before reading the rest of this post, as it is largely a response to that.
Now, on to Charles section on Rubinius:
Evan Phoenix’s Rubinius project is an effort to implement Ruby using as much Ruby code as possible. It is not, as professed, “Ruby in Ruby” anymore. Rubinius started out as a 100% Ruby implementation of Ruby that bootstrapped and ran on top of MatzRuby. Over time, though the “Ruby in Ruby” moniker has stuck, Rubinius has become more or less half C and half Ruby. It boasts a stackless bytecode-based VM (compare with Ruby 1.9, which does use the C stack), a “better” generational, compacting garbage collector, and a good bit more Ruby code in the core libraries, making several of the core methods easier to understand, maintain, and implement in the first place.
A little background is in order, to put things straight. Rubinius began as a hobby, back in February of 2006 (Same month I got married, that’s how I recall).
At RubyConf 2006, I gave a presentation on what was then the initial work, which at that point constitute 3 bodies of work.
- A VM written in ruby, using RubyInline to access some raw operations. More slow that you can imagine.
- A VM written in C, created by hand translating the ruby code into C. Parts of this work were originally done using a translator program I’d written, which tried to convert the VM in ruby into C mechanically. This proved beyond my skill and time level, thus I felt it was more important to have something running.
- A kernel of ruby code, implementing 95% of the core library / kernel / class library of 1.8. The terminology for this part has always been fuzzy in the Ruby community. Rubinius calls this the kernel, some call it the standard library, some the class library. It’s the implementations of the builtin classes such as Array, Hash, etc.
It’s plainly true that today, the VM is about 22,000 lines, the kernel 23,000 lines. I’ve never hidden this fact from anyone; in fact I’ve put those numbers directly into presentations. That’s been true for pretty much the entire life of the project in the public. The initial ruby prototype was only even run by me.
I do though believe that it still can claim “Ruby in Ruby”. When I present on Rubinius or am asked about this, the response I give is:
What is Ruby?
The typically response is that Ruby is 3 things:
- A syntax
- An execution model
- A kernel
Again, lets have some context. When I began this project, there was buzz about improving things like String and Array. In 1.8, this requires diving down into C right off the bat. Plus, consider languages such as C++ and Java. Java largely claims to be written in Java, since almost the entire class library is written in Java. This lets it evolve faster, because there is no mismatch between Java user code and the Java class library.
It is this that we typically talk about “Ruby in Ruby”. If I’ve not explained this well enough in person and in type, I take full responsibility for this misunderstanding.
There is the long term goal of having a VM which is mechanically generated from Ruby code, in the same way Squeak’s VM is written. But after that RubyConf 2006, there has been no additional work on this, but there is a very good reason for that.
Rubinius today has around 150 people who have received commit rights. The vast, vast majority of their work has been in the kernel, because this is the largest part of the whole system. And probably 95% of that work has been writing Ruby code. This means that for pretty much all contributers, helping with Rubinius means writing Ruby code. And thus to them, it is Ruby in Ruby.
The promise of Rubinius is pretty large. If it can be made compatible, and made to run fast, it might represent a better Ruby VM than YARV. Because a fair portion of Rubinius is actually implemented in Ruby, being able to run Ruby code fast would mean all code runs faster. And the improved GC would solve some of the scaling issues Ruby 1.8 and Ruby 1.9 will face.
Rubinius also brings some other innovations. The one most likely to see general visibility is Rubinius’s Multiple-VM API. JRuby has supported MVM from the beginning, since a JRuby runtime is “just another Java object”. But Evan has built simple MVM support in Rubinius and put a pretty nice API on it. That API is the one we’re currently looking at improving and making standard for user-land MVM in JRuby and Ruby 1.9. Rubinius has also shown that taking a somewhat more Smalltalk-like approach to Ruby implementation is feasible.
But here be dragons.
In the 1.5 years since Rubinius was officially named and born into the Ruby world, it has not yet met any of these promises. It is not generally faster than Ruby 1.8, though it performs pretty well on some low-level microbenchmarks. It is not implemented in Ruby: the current VM is written in C and the codebase hosts as much C code as it does Ruby code. Evan’s work on a C++ rewrite of the VM will make Rubinius the first C++-based Ruby implementation. It has not reached the Rails singularity yet, though they may achieve it for RailsConf (probably in the same cobbled-together state JRuby did at JavaOne 2006…or maybe a bit better). And the second Rails inflection point–running Rails faster than Ruby 1.8–is still far away.
Charles once again gets my hackles up, thought his points are true. We’ve yet to run rails. We’ve yet to run significant Ruby code faster than 1.8. I am finishing up a C++ rewrite of the VM.
I’ve addressed the Ruby in Ruby phraseology above, so lets move past that.
Performance is improving at a slow, regular pace. This is because of 2 factors:
- VM improvements. Adding more caches, more VM logic to make it’s constructs faster. This happens far more infrequently than:
- Ruby code improvements. This happens quite often, because we have so many people working in the kernel. These kinds of improvements will get us a long way, but not the entire way to 1.8 performance. That’s where VM improvements help.
Again, he brings up the sizes of the VM in comparison to the kernel. This will be the last time I address this in this post. Ruby is a dynamic language, which boasts a very rich, featureful kernel. It’s syntax and constructs allow for short, succinct algorithms.
So while, yes, the kernel is the same number of lines as the VM, it’s not unreasonable to say that it probably constitutes 10x the functionality. This is because the written Ruby code is shorter and easier to understand. That’s the whole point of this project, to make the core of it easier to work on and evolve.
Compatibility is not going to be a problem for Rubinius. They’ve worked very hard from the beginning to match Ruby behavior, even launching a Ruby specification suite project to officially test that behavior using Ruby 1.8 as the standard. I have no doubt Rubinius will be able to run Rails and most other Ruby apps people throw at it. And despite Evan’s frequent cowboy attitude to language compatibility (such as his early refusal to implement left-to-right evaluation ordering, a fatal decision that led to the current VM rework), compatibility is likely to be a simple matter of time and effort, driven by the spec suite and by actual applications, as people start running real code on Rubinius.
A quick personal response to a personal attack. I never once refused to implement left-to-right evaluation ordering, this is a bald faced lie. It’s totally true that Rubinius today is right-to-left, because that was much easier to implement way back in the day when the project began. As we started to work on ActiveRecord, we found that there was code that appear to depend on left-to-right ordering, so I brought it up with matz. And now I’m in the midst of changing it. Truth be told, I should have done my research back when the project started, it would have been easier to fix this then than now.
But I take issue with Charles statement that I’m operating fast and loose with language compatibility. We have an awesome team working on RubySpecs, which will end up being a definitive reference for 1.8 behavior. I will always be the first one to defend their behavior, and get Rubinius implementing it properly.
That’s not to say that Rubinius in the past has made temporary pragmatic decisions in implementation. We absolutely have, and in time those are corrected.
Perhaps Charles mistakes my pragmatism and Montana upbringing for a cowboy attitude.
Performance is going to be a much harder problem for Rubinius. In order for Rubinius to perform well, method invocation must be extremely fast. Not just faster than Ruby 1.8 or Ruby 1.9, but perhaps an order of magnitude faster than the fastest Ruby implementations. The simple reason for this is that with so much of the core classes implemented in Ruby, Rubinius is doing many times more dynamic invocations than any other implementation. If a given String method represents one or two dynamic calls in JRuby or Ruby 1.8, it may represent twenty in Rubinius…and sometimes more. All that dispatch has a severe cost, and on most benchmarks involving heavily Ruby-based classes Rubinius has absolutely dismal performance–even with call-site optimizations that finally pushed JRuby’s performance to Ruby 1.9 levels. A few benchmarks I’ve run from JRuby’s suite must be ratcheted down a couple orders of magnitude to even complete.
He’s absolutely correct. We have a ways to go, but I don’t believe we can’t get there. Others before us have made it work, and I think so shall we.
And the Rubinius team knows this. Over the past few months, more and more core methods have been reimplemented in C as “primitives”, sometimes because they have to be to interact with C-level memory and VM constructs, but frequently for performance reasons. So the “Ruby in Ruby” implementation has evolved away from that ideal rather than towards it, and performance is still not acceptable for most applications. In theory, none of this should be insurmountable. Smalltalk VMs run significantly faster than most Ruby implementations and still implement all or most of the core in Smalltalk. Even the JVM, largely associated with the statically-typed Java language, is essentially an optimized dynamic language VM, and the majority of Java’s core is implemented in Java…often behind interfaces and abstractions that require a good dynamic runtime. But these projects have hundreds of man-years behind them, where Rubinius has only a handful of full-time and part-time enthusiastic Rubyists, most with no experience in implementing high-performance language runtimes. And Evan is still primarily responsible for everything at the VM level.
Of course, it would be folly to suggest that the Rubinius team should focus on performance before compatibility. The “Ruby in Ruby” meme needs to die (seriously!), but other than that Rubinius is an extremely promising implementation of Ruby. Its performance is terrible for most apps, but not all that much worse than JRuby’s performance was when we reached the Rails singularity ourselves. And its design is going to be easier to evolve than comparable C implementations, assuming that people other than Evan learn to really understand the VM core. I believe the promise of Rubinius is certainly great enough to continue the project, even if the perils are going to present some truly epic challenges for Evan and company to overcome.
Thank you for the kind works of encouragement Charles. We’re getting there.
I want to say briefly as well that Charles and I are good friends, I just wanted to clear the air slightly and get everyone on the same page.
super is your friend
Sitting here in Copenhagen, at RubyFools, I thought I’d share a technique that I’ve known about for some time, but seems to not have gotten into the normal ruby vernacular.
This trick is the use of super in methods contained in a Module. Consider the following code:
module N
def go
puts "N#go"
super
end
end
class B
def go
puts "B#go"
end
end
class A < B
include N
end
A.new.go
Will print:
N#go B#go
This is HIGHLY useful implementing rails plugins, where normally you’d use alias_method_chain to change a method directly inside ActiveRecord::Base. Instead, simply call super in the method that provides the new functionality, and when your module is included in your module class (which is a subclass of ActiveRecord::Base), and the main, ActiveRecord::Base implementation will be called.
NOTE: This trick only works if the method you wish to wrap is located in a superclass of the class you have defined the module in. IE, if N were included in B directly rather than A, N#go would never be called.
Ode to Airport Security
Oh how I love, to stand in line
I stood here so long, I came up with this rhyme
We curse and we shuffle, from left to right
Seems like we’ll be here all damn night
The problem is hard, it’s NP complete
But after seven years, you’d think it’d be beat
I’m finally through, waiting here at the gate
When I arrive, I won’t know the date.
Apple TV 2.0
I have to gush about my favorite feature of the Apple TV upgrade. It’s a feature I’ve wanted since Apple TV came out. You can now use an Apple TV as remote AirTunes speakers!!
I love it.
Day to day rubinius
The venerable Eric Hodel (drbrain) has whipped the rubinius team into blogging more, so this should be the first of many posts to come.
Development front
We continue to push forward, getting rubinius running everyday ruby code. I continue to use irb under rubinius daily, and it’s proved quite stable.
Though, we’ve begun to hit the standard lib code that is quite tricky. Case in point, mathn.rb. mathn.rb adds some new methods to Fixnum and Bignum, as well as redefining some very core methods, like, Fixnum#/ (divide). We’re currently working through how to support this, because as is, when mathn redefines that method to start returning Floats, most of the rubinius system goes south. Thats because the kernel currently assumes that if it uses Fixnum#/, a Fixnum is returned, and it can pass that to other primitives and such.
We’re still unsure about the long term solution for this, but it does bring up a problem with having a very open, dynamic language, where the kernel of the language uses that same open, dynamic runtime. A user can change the behavior of a core, system method, and effect a lot more than they could in MRI. This is the famous double edged sword.
My hope is that we’re able to code the kernel a little defensively and really stabilize the kernel to the point that these kinds of changes wont cause the whole system to go south.
Conference front
Seems that 2008 is going to be the year of conferences for me.
- On Feb 8th and 9th, Ezra and I will be at acts_as_conference, giving a charity tutorial talk about how to be a better ruby developer.
- Next, I’ve been invited to give at talk about rubinius, as well as the “Party” keynote (not sure what the party part is actually) at RubyFools over in Copenhagen, Denmark. This should be a lot of fun for a few reasons.
- I’ll be giving a non-Rubinius specific talk for once (the keynote), which is something I’ve wanted to do for a while.
- I’ve never been to Denmark, so it’s always fun to visit a new city.
- Abby is coming too! She’s going to sightsee and such while I’m at the conference, and we’ll have a few days before and after to take day trips and such. She loves europe, it should be a great time.
- There a bunch after this, but I’m not yet sure which I’m going to. The total is something like 5 or 6 before June 1st, so it’s going to be busy busy busy no matter what.
More to come!