September 2, 2008...10:53 pm

Ruby on V8

Jump to Comments

V8Google 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

  • God I love open source + hacking. Sweet work, even if your benchmark proves dodgy or not, very nice.

  • 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.

  • @drnic thx!

    @pierre but the thing is, I want to write my code in Ruby! I love Ruby you know ;)

  • 홍민희의 생각…

    Ruby on V8!!! — “10 times faster then YARV and faster then C!”…

  • 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.

  • 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.

  • 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.

  • 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).

  • 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. :)

  • 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.

  • 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.

  • 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

  • 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!

  • @ 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.

  • @emil, ah you’re right, now that I think more about it! Sorry about this, I’ll update the post

  • 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)])

  • I’d like to see a comparision with RubyJS. I’d expect ruby compiled into javascript to run faster than a ruby bytecode interpreter.

  • 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.

  • @alex farran: I tried that, it’s a lot slower, I think because RubyJS generated code is not optimized.

  • The ruby version is slow code.

    By replacing += with <<, it becomes faster tens of times.
    And HotRuby is optimized for String += String.

  • The version at http://hotruby.yukoba.jp/benchmark/bm_loop_times.html
    uses ‘+=’ because ‘<<’ gives this error:

    “Undefined Function: <<”

  • 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

  • @fumbo wow, thx a lot for the info very interesting!

  • 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

  • How about perl version?
    perl -lwe ‘my $sum; $sum.=$_ foreach (0..49999);’

  • [...] 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. [...]

  • 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 :D Keep up the good work Marc-Andre!

    Cheers,
    Ninh

  • @Ninh seems like we share a couple interests hey ;)

  • [...] Ruby on V8 – Ja, die V8 engine is leuk met Javascript, maar je kan er met wat moeite ook Ruby op draaien! [...]

  • 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

  • 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

  • ~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();

  • 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.)

  • [...] Ruby on V8 « Marc-André Cournoyer’s blog [...]

  • drink advertisement for internet

  • 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)

  • [...] Ruby on V8 « Marc-André Cournoyer’s blog (tags: programming v8) [...]

  • checksinthemail: JRuby has worked client-side, in the browser, or as a desktop application since 2006.

  • [...] 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 [...]

  • 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.

  • [...] bin beim Frühstückskaffee über einen digg-Eintrag gestolpert. ruby on V8 Klingt spannend und ich habe einen neuen Blog auf meiner Watch [...]

  • [...] Spreadsheet を使っていたのですが、何人かの方からこれを勧められました。Ruby on V8 « Marc-André Cournoyer’s blog 2008 年 9 月 8 日rbv8: Ruby スクリプトを JavaScript に変換して v8 [...]

  • Looks like JRuby has C on the run! It only takes 2500 times as long to run. How will C ever compete with that?

  • 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.

  • [...] 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 [...]

  • [...] 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. [...]

  • [...] 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 [...]

  • Your font is *way* too small.

  • 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!

  • 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;
    }

  • 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;
    }

  • [...] 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. [...]


Leave a Reply