Google Chrome looks cool, ok… But what is even cooler for me is V8, the super JavaScript VM.
JavaScript is a dynamic language, just like Ruby.
You can add stuff to objects at runtime, like Ruby.
It’s object oriented, like Ruby.
It has a GC, like Ruby.
…
What if we could run Ruby on V8?
Well, it’s a lot easier that you think. If you remember a while ago, someone released HotRuby. It runs YARV bytecode in the browser.
So I plugged the 2 together just to see what would happened => rbv8.
It’s fast (sometimes)
I used the script on HotRuby site to benchmark.
sum = ""
50000.times{ |e| sum += e.to_s }
And just for fun, I also wrote it in C:
int main (int argc, char const *argv[])
{
char *str = malloc(sizeof(char) * 238890);
char buf[5];
size_t i;
for (i = 0; i < 50000; ++i) {
sprintf(buf, "%d", i);
strcat(str, buf);
}
return 0;
}
Update: seems like my C code was the suck, thx for some commenters for pointing it out. Here’s a better version which is way faster (thx to Hongli Lai):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main (int argc, char const *argv[])
{
char *str = malloc(sizeof(char) * 238890);
char buf[5];
size_t i;
unsigned long last = 0;
for (i = 0; i < 50000; ++i) {
int len = sprintf(buf, "%d", i);
memcpy(str + last, buf, len);
last += len;
}
return 0;
}
Also here’s a Javascript version:
var sum = "";
for (var i = 0; i < 50000; i++) {
sum += i.toString();
};
C: 0.017 sec Javascript: 0.063 sec rbv8: 0.987 sec Firefox 3: 3.636 sec Safari 3: 4.368 sec Opera 9.50: 4.679 sec Ruby 1.8.6: 9.565 sec Ruby 1.9.0: 9.669 sec Rubinius 0.8.0: 15.576 sec JRuby 1.1 b1: 42.691 sec
OMG OMG OMG OMG!!! 10 times faster then YARV and faster then C!
Update: Ok… if your replace += w/ <<, YARV beats the Javascript version running on V8, thx to Nobu Nakada for noting this
But wait, don’t go tell your friends yet! It seems, that all the other benchmarks I tried were slower (sometimes by a very wide margin).
But I think this means that the potential is there, it just need to be exploited properly.
Also another fun thing to note, is that if you change Fixnum#times to while i < 50000 in the Ruby code, it becomes a lot slower. No idea why.
Me wants to try
If you wanna try rbv8:
You need Ruby 1.9 installed as `ruby19` and make sure you meet V8 Pre-requisites: http://code.google.com/apis/v8/build.html#pre_reqs.
git clone git://github.com/macournoyer/rbv8.git
cd rbv8
rake
bin/rbv8 sample/concat.rb
Note that this is just a prototype. I just hacked this in an hour to benchmark it. See the README file for what needs to be done next.









55 Comments
September 2, 2008 at 11:05 pm
God I love open source + hacking. Sweet work, even if your benchmark proves dodgy or not, very nice.
September 2, 2008 at 11:08 pm
There’s also this:
http://steve-yegge.blogspot.com/2007/06/rhino-on-rails.html
It’s also an excellent blog in general, I recommend it.
September 2, 2008 at 11:12 pm
@drnic thx!
@pierre but the thing is, I want to write my code in Ruby! I love Ruby you know
September 3, 2008 at 12:55 am
홍민희의 생각…
Ruby on V8!!! — “10 times faster then YARV and faster then C!”…
September 3, 2008 at 2:04 am
Uhm, it’s quite an interesting thought to run ruby on v8, but why on earth would you benchmark such a silly thing as string concatenation? Try makeing a proper benchmark, something like making a new class, multiple inheritance, class instansiation.
And for the love of god, don’t ever claim to be faster than gcc – it knows better than you.
September 3, 2008 at 2:09 am
In follow-up to my last comment, I also discovered that your C version has memory corruption problems and may crash. Here’s a fixed C version which is also faster:
http://pastie.org/265056
On my system, it runs in 0.03 seconds — 32 times faster than V8.
September 3, 2008 at 2:13 am
Your C version is a little under optimized. The strcat does a strlen on your increasingly long string. Try this one:
#include
#include
int main (int argc, char const *argv[])
{
char *str = malloc(sizeof(char) * 238890);
char buf[5];
size_t i;
int ofs = 0;
for (i = 0; i < 50000; ++i) {
ofs += sprintf(str+ofs, “%d”, i);
}
return 0;
}
On my box, this version drops from 2.2s to 0.02s. I’m sure any competitor has a real string class which knows its length, so this hardly feels like a cheat as far as optimizations go.
September 3, 2008 at 2:16 am
Um, while I love the comparison, and find the results very interesting, there are a couple of caveats with the C code:
* The length of your ’str’ buffer is incorrect, it doesn’t include space for the terminator NIL character. The proper length for the buffer is (at least) 238891.
* You cannot be certain that ’str’ is cleared after allocation, so there should be a str[0] = ”; statement before the loop to make sure the output string is properly empty at first.
* Again, there is no space allocated for the terminator char in the ‘buf’ buffer used to format the decimal number. It should be at least char buf[6], since “50000″ is five chars.
* There is no need to include sizeof (char) in the malloc() call, as it is 1. Always.
Second, I wonder if V8 stores string lengths for its strings, thus allowing it to do string concatenation without first checking the length of the incoming string, as strcat() must do. Recoding the C program to track the length of ‘buf’ during the loop is left as an exercise for the reader (hint: on my test system, the performance increased by a factor of 200).
September 3, 2008 at 2:19 am
Bleh, the blog system ate my backslash-zero in my second bullet point. Also, I see that I must code faster to get First Post, heh. Good to see there are more watchful C eyes out there.
September 3, 2008 at 2:37 am
Indeed. The original C here runs in 1.14s; an optimized one similar to Hongli’s runs in just 0.01s. strcat() is *very* expensive, since it has to scan through the string looking for 00 each time; V8 will just have a length attached to the object.
September 3, 2008 at 2:37 am
inspiring. thanks!
September 3, 2008 at 4:59 am
In this case I think it’s far more likely that HotRuby isn’t actually working. The amount of work required to even come close to having the most basic closures and .times function would preclude sub-second execution times. My guess is that it’s just broken.
September 3, 2008 at 5:14 am
I ported your solution to Java, which can be found at http://pastebin.com/m4f022c37
I only compared it to your C solution, which is as others pointed out, is a tad incorrect. On my machine, the C program ran in approx 3.3 seconds while Java finished in between 0.13 to 0.18 seconds, with VM startup (client vm, version 1.5). So if extrapolate that to your numbers, Java is several times faster than v8-ruby.
Again as pointed out before, the right C version probably beats Java. Just wanted to ooze some of my Java love in here.
regards,
Guðmundur Bjarni
September 3, 2008 at 6:04 am
hey all! thx for the comments!
I know the C code is not optimized. But if I optimize it like some of you recommend, I don’t think it would be a fair benchmark cause the C version would “know” upfront the len of some strings which the Ruby version does not.
Of course C code can be made faster then Ruby any time. My claim is mostly that “equivalent” code running dynamically on V8 is faster then compiled code on gcc.
@Mads Sülau Jørgensen: I just used the benchmark on hotruby’s site. I know it’s not the best benchmark. It’s just the most impressive one (yes I cheated). If you think gcc is the fastest C compiler you should do some research.
@Charles Oliver Nutter: I know it’s hard to believe it’s even running but, I added a puts sum at then end and compared:
bin/rbv8 sample/concat.rb > rbv8.out
ruby sample/concat.rb > rb.out
diff rbv8.out rb.out
no diff!
September 3, 2008 at 6:13 am
@ macournoyer #14: if the JavaScript implementations represent strings in a way that includes their length, which your C version does not, then the comparison is unfair in the *other* direction.
Every time you call strcat(), the C standard library must walk the string until it finds the terminating NUL character, that is the *only* way to find the length of the string.
Compare that to the (assumed) pre-computed current length available to the JavaScript engine, and you can see that it is unfair to the C version. The suggested solution, using the return value of sprintf() and adding it to a running count of the output string’s current length, is not a cheat.
September 3, 2008 at 6:26 am
@emil, ah you’re right, now that I think more about it! Sorry about this, I’ll update the post
September 3, 2008 at 6:59 am
Try the python version, which on my box, beats the ruby version.
#!/usr/bin/env python
sum = “”.join([str(x) for x in xrange(50000)])
September 3, 2008 at 7:52 am
I’d like to see a comparision with RubyJS. I’d expect ruby compiled into javascript to run faster than a ruby bytecode interpreter.
September 3, 2008 at 8:08 am
root, the examples here force the creation of new string objects at each step, rather than relying on a natural concatenator of the language. A similar approach in Ruby (`sum = (0…50000).map(&:to_s).join`) is slightly faster than your Python version when using YARV here.
But string concatenation benchmarking is silly, anyways. At best, it tests the particular implementation of strings (which depends on the nature of your language) and memory allocation. It has very little to do with the usually-measured interpreter and overall speed.
September 3, 2008 at 8:10 am
@alex farran: I tried that, it’s a lot slower, I think because RubyJS generated code is not optimized.
September 3, 2008 at 8:18 am
The ruby version is slow code.
By replacing += with <<, it becomes faster tens of times.
And HotRuby is optimized for String += String.
September 3, 2008 at 9:23 am
The version at http://hotruby.yukoba.jp/benchmark/bm_loop_times.html
uses ‘+=’ because ‘<<’ gives this error:
“Undefined Function: <<”
September 3, 2008 at 9:46 am
The potential is much closer than you think. v8 is led by the authors of the StrongTalk VM (the fastest smalltalk vm and also opensource). Given that Ruby and Smalltalk are first cousins all that is really lacking in the Ruby community s the depth of vm developers able to port the Strongtalk code.
There is much more background on the origins of the v8 here
http://astares.blogspot.com/2008/09/google-chrome-javascript-and-smalltalk.html
September 3, 2008 at 9:54 am
@fumbo wow, thx a lot for the info very interesting!
September 3, 2008 at 11:36 am
Using the Johnson gem in ruby 1.8.6:
https://github.com/jbarnette/johnson/tree
require ‘johnson’
rt = Johnson::Runtime.new
rt.evaluate(<<-eojs)
var sum = “”;
for(var i = 0; i < 50000; i++) {
sum += i.toString();
}
eojs
puts rt['sum']
My time:
real 0m0.254s
user 0m0.114s
sys 0m0.020s
September 3, 2008 at 11:49 am
How about perl version?
perl -lwe ‘my $sum; $sum.=$_ foreach (0..49999);’
September 3, 2008 at 1:19 pm
[...] And for a bit of fun, Marc-Andre Cournoyer tied together HotRuby (remember that? the beast that runs YARV code in the browser!) and V8 to create fast Ruby in the browser. [...]
September 3, 2008 at 1:21 pm
Oh man, Hongli and I were talking about the possibilities of running Ruby on V8 a few hours ago as well through the use of Hotruby. Seems you’ve beat us to the race
Keep up the good work Marc-Andre!
Cheers,
Ninh
September 3, 2008 at 2:08 pm
@Ninh seems like we share a couple interests hey
September 3, 2008 at 2:40 pm
[...] Ruby on V8 – Ja, die V8 engine is leuk met Javascript, maar je kan er met wat moeite ook Ruby op draaien! [...]
September 3, 2008 at 3:28 pm
Use FStringCat when concatting strings in Javascript !!
much faster !
function FStringCat()
{
var accum = ”;
var list = [];
this.push = function(what)
{
accum += what;
if(accum.length>2800)
{
list.push(accum);
accum = ”;
}
};
this.value = function()
{
list.push(accum);
accum = ”;
list = [ list.join("") ];
return list[0];
};
}
f.push( “text” )
f.value() //test
September 3, 2008 at 3:31 pm
console.time(”p”);
var sum = new FStringCat();
for (var i = 0; i < 50000; i++) {
sum.push( i.toString() );
};
var p = sum.value();
console.timeEnd(”p”);
time : 328ms
Firefox2 – Intel Core2Duo E8400 @ 3ghz
September 3, 2008 at 3:42 pm
~30ms in Chrome:
var p=new Date(); var sum = “”; for (var i = 0; i < 50000; i++) {sum+=i.toString();} (new Date()).getTime()-p.getTime();
30ms in Chrome:
var p=new Date(); var sum = new FStringCat(); for (var i = 0; i < 50000; i++) {sum.push( i.toString() );};var f = sum.value(); (new Date()).getTime()-p.getTime();
September 3, 2008 at 4:43 pm
An improved Python version:
sum = “”.join(map(str, xrange(50000)))
Previously suggested version:
$ python -m timeit -n 100 ’sum = “”.join([str(x) for x in xrange(50000)])’
100 loops, best of 3: 42.7 msec per loop
My version:
$ python -m timeit -n 100 ’sum = “”.join(map(str, xrange(50000)))’
100 loops, best of 3: 32 msec per loop
Reason: It’s faster to let map create the list as it, at C level, can preallocate a list by the exact length of the sequence and fill it in as it maps. It also maps at C level, making these C function calls and taking a lot of Python-level stuff out of the equation.
(Pardon WordPress being awkward and making these quotes into something they’re not supposed to be.)
September 4, 2008 at 2:10 am
[...] Ruby on V8 « Marc-André Cournoyer’s blog [...]
September 4, 2008 at 6:50 am
[...] Ruby on V8 « Marc-André Cournoyer’s blog [...]
September 4, 2008 at 10:14 am
drink advertisement for internet
September 4, 2008 at 11:34 am
A comparison to the only other possible client-side Ruby engine, IronRuby would be good (or could be bad, and we’ll have to hush up the results)
September 4, 2008 at 5:31 pm
[...] Ruby on V8 « Marc-André Cournoyer’s blog (tags: programming v8) [...]
September 4, 2008 at 11:56 pm
checksinthemail: JRuby has worked client-side, in the browser, or as a desktop application since 2006.
September 5, 2008 at 5:18 pm
[...] JavaScript-based virtual machine that can run YARV-compiled Ruby code, and he set out to test the performance of Ruby code running on HotRuby on top of V8. The results are far from scientific and many commenters have pointed out flaws, but Marc’s [...]
September 6, 2008 at 8:11 am
sorry, in my poor english.
sample c code has buffer over run bug.
char buf[5];
sprintf(buf, “%d”, 10000);
please look this code.
char buf[5 + 1];
sprintf(buf, “%d”, 10000);
see you.
September 7, 2008 at 3:06 pm
[...] bin beim Frühstückskaffee über einen digg-Eintrag gestolpert. ruby on V8 Klingt spannend und ich habe einen neuen Blog auf meiner Watch [...]
September 8, 2008 at 4:09 am
[...] Spreadsheet を使っていたのですが、何人かの方からこれを勧められました。Ruby on V8 « Marc-André Cournoyer’s blog 2008 年 9 月 8 日rbv8: Ruby スクリプトを JavaScript に変換して v8 [...]
September 8, 2008 at 8:37 am
[...] http://macournoyer.wordpress.com/2008/09/02/ruby-on-v8/ : un prototype pour interpréter du code Ruby dans la machine virtuelle V8 [...]
September 9, 2008 at 6:22 am
Looks like JRuby has C on the run! It only takes 2500 times as long to run. How will C ever compete with that?
September 9, 2008 at 10:25 pm
Well, code speed is not the only variable in most applications. Sometimes it doesn’t even get considered.
In those cases where speed is not important, Ruby can easily compete against C in programmer’s productivity.
This is the reason why this blog is not made in assembler.
September 10, 2008 at 11:24 am
[...] Google’s open source JavaScript engine is also very interesting. Marc-André Cournoyer asked What if we could run Ruby on V8? well he managed it and its [...]
September 11, 2008 at 10:27 am
[...] Some people sensed the potential of V8. There are a lot of script language in the world. Javascript is ont of them, and many languages have similar features like a Garbage Collection. Ruby is one of them. rbv8 is a ruby vm running on v8. rbv8 shows amazing performance. In some case, rbv8 runs about 10 times faster than official ruby vm. Here is the benchmark of rbv8. Another article talks that V8 is little slower than Psyco, python compiler extension module. I think it is also applicable to many other language. [...]
September 14, 2008 at 6:11 pm
[...] gain to web scripting languages. First, the v8 engine is very fast compared to ruby in this very simple benchmark. Whether JavaScript drags Ruby and Python in Webapps or not – the now open sourced techniques of v8 [...]
September 17, 2008 at 6:06 pm
Your font is *way* too small.
September 25, 2008 at 6:38 pm
I ran the “heavy benchmark” on the hotruby link at the top of this page. On my Ubuntu Hardy laptop, the FF3 and Crossover Chromium results were somewhat surprising:
FF3 = ~5 sec
Crossover Chromium = ~0.99 sec
A very contrived example but the results are still impressive!
September 25, 2008 at 8:31 pm
Here’s a faster C version (about 2x). gcc doesn’t support ltoa, so I’ve added one from K&R. If anyone is interested, I have another that’s almost twice as fast again, but uses a hacked ltoa.
Enjoy!
#include
#include
#include
/**
* Ansi C “itoa” based on Kernighan & Ritchie’s “Ansi C”
* with slight modification to optimize for specific architecture:
*/
void strreverse(char* begin, char* end)
{
char aux;
while(end>begin)
aux=*end, *end–=*begin, *begin++=aux;
}
void itoa(int value, char* str, int base)
{
static char num[] = “0123456789abcdefghijklmnopqrstuvwxyz”;
char* wstr=str;
int sign;
div_t res;
// Validate base
if (base35) {
*wstr=”;
return;
}
// Take care of sign
if ((sign=value) < 0)
value = -value;
// Conversion. Number is reversed.
do {
res = div(value,base);
*wstr++ = num[res.rem];
} while (value=res.quot);
if(sign<0)
*wstr++=’-';
*wstr=”;
// Reverse string
strreverse(str,wstr-1);
}
int main()
{
char *str = malloc(sizeof(char) * 238890 + 1); // +1 for final null
char *next = str;
int i;
for ( i = 0; i < 50000; i++ ) {
itoa(i,next,10);
next += strlen(next);
}
return 0;
}
September 25, 2008 at 10:29 pm
And we shouldn’t overlook C++ which comes in midway between my C sample and Marc-André’s improved version.
And it’s not much longer than the Ruby version.
#include
int main()
{
std::stringstream s;
for (int i=0; i<50000; i++)
s << i;
return 0;
}
June 10, 2009 at 7:09 pm
[...] best them both in speed and possibly even memory. Even when Ruby bytecode is run on V8 it can be almost as fast as Ruby 1.9. Both Python and Ruby have problems scaling using threads because of their GILs. [...]