Ruby on V8

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.

64 Comments

Filed under ruby

64 responses to “Ruby on V8

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

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

  3. @drnic thx!

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

  4. Pingback: dahlia's me2DAY

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

  6. Hongli Lai

    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.

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

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

  9. 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. 🙂

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

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

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

  13. 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!

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

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

  16. root

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

  17. Alex Farran

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

  18. Daniel Luz

    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.

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

  20. Nobu Nakada

    The ruby version is slow code.

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

  21. Mike Berrow

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

    “Undefined Function: <<“

  22. Fumbo

    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

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

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

  25. sin

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

  26. Pingback: Ajaxian » Brendan discusses how TraceMonkey is climbing faster; Ruby on the Web with V8

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

  28. @Ninh seems like we share a couple interests hey 😉

  29. Pingback: Google Chrome Link Roundup | Scriptorama.nl

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

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

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

  33. Somebody

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

  34. Pingback: Daily Digest for 2008-09-03 | Pedro Trindade

  35. Pingback: Pedro Trindade » Blog Archive » Daily Digest for 2008-09-03

  36. nicholas1234

    drink advertisement for internet

  37. checksinthemail

    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)

  38. Pingback: links for 2008-09-04 « 個人的な雑記

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

  40. Pingback: Could JavaScript Power A Significant Future Ruby Implementation or VM?

  41. umedoblock

    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.

  42. Pingback: cadego blog» Blogarchiv » ruby meets javascript oder ruby on V8 (rbv8)

  43. Pingback: 脇田 建 · Bookmarks for 2008-9-7 through 2008-9-8

  44. Pingback: Dev Blog AF83 » Blog Archive » Veille technologique : Google chrome, V8, Django 1.0, etc.

  45. Al Brown

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

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

  47. Pingback: Nektra Advanced Computing Blog :: Google Chrome

  48. Pingback: Everyone and Everything » Blog Archive » Google Chrome is just result. Real power is from V8

  49. Pingback: Helmuts Blog » Blog Archive » Server Side v8 JavaScript

  50. Sean

    Your font is *way* too small.

  51. 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!

  52. 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;
    }

  53. 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;
    }

  54. Pingback: Hacking Devin Torres» Blog Archive » JavaScript: The Fastest Dynamic Language?

  55. Stupid reality check but how useful is this? Per the HotRuby site (http://hotruby.yukoba.jp/) there are many language features absent.

    Is HotRuby development ongoing somewhere else?

  56. Ebow

    Still being unfair to C because of the malloc call when all the other examples use a hard-coded number. Malloc is slow! Try it again without malloc and let’s see how much of a difference it makes.

    #include
    #include
    #include

    int main (int argc, char const *argv[])
    {
    char str[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;
    }

  57. You actually make it seem so easy together with your presentation but I find this matter to be really one thing which I think I would never understand. It sort of feels too complex and extremely vast for me. I’m having a look forward to your next put up, I will try to get the dangle of it!

  58. I just came across your blog about V8. Cool stuff.

    Since V8 is a VM, it seemed to me that other languages could be ported to the browser in the future. You mentioned Ruby here. Are there any references to other languages that run directly in the V8 engine?

    It would fantastic (imo) for additional languages to run natively in the browser.

    Frank

  59. What

    sum = ”; 50000.times{ |e| sum << e.to_s }

  60. It was difficult to find your site in google. You should create some high page rank contextual backlinks in order to rank your
    website. I know – writing articles is very time consuming, but contextual backlinks are the best type of backlinks.

    I know very useful tool that will help you to create
    unique, readable content in minute, just search in google – laranita free content source

Leave a reply to Daniel Cadenas Cancel reply