Rubinius 1.0.0 が速すぎてびっくりした

(追記: Rubiniusとは、Ruby自身で書かれたRubyの処理系。Javaで書かれているJRubyとともに、期待を集めているRuby処理系のひとつ。)
そもそもこのブログは Rubinius で遊んだ結果を紹介するために始めたようなものだったのに、せっかく Rubinius 1.0.0 がリリースされたのにスルーしてた (ごめんよ Evan)。

ようやく Rubinius をインストールしてベンチマークをとったので、衝撃的な結果とともに紹介する。


インストール

インストールは簡単。Web サイトからダウンロードし、コンパイルするだけ。Mac OS X ならバイナリも用意されているけど、今回は使用せず、自分でコンパイル&インストール。なおコンパイルには Rake を使うので、Rubinius をコンパイルするには Ruby が必要。

### Mac OS X 10.6 で実験
$ wget http://rubini.us/download/latest
$ tar xzf rubinius-1.0.0-20100514.tar.gz
$ cd rubinius-1.0.0/
$ ./configure --prefix=/usr/local/ruby/rubinius-1.0.0
$ rake
$ rake install
$ export PATH=$PATH:/usr/local/ruby/rubinius-1.0.0
$ rbx -v    # コマンド名は rbx
rubinius 1.0.0 (1.8.7 release 2010-05-14 JI) [x86_64-apple-darwin10.3.0]

コンパイルでめちゃメモリ喰うわ。C++ でのコンパイルってこんなに重くてメモリ喰いだったのか。そりゃ Google がコンパイルの速い言語を欲しがるわけやわ

ベンチマーク: フィボナッチ数列

現実のアプリケーションではまず使われないくせにベンチマークでは必ず登場するフィボナッチ数列を実行してみた。


test1.rb

def f(n)
  if n <= 1
    return 1
  else
    return f(n-1) + f(n-2)
  end
end

require 'benchmark'
n = ($n || 34).to_i
Benchmark.bm(10) do |x|
  x.report(RUBY_VERSION) { f(n) }
end


実行結果は驚くべき結果に!

### Ruby 1.8.7
$ ruby -v
ruby 1.8.7 (2010-01-10 patchlevel 249) [i686-darwin10.2.0]
$ /usr/bin/time ruby test1.rb
                user     system      total        real
1.8.7      14.400000   0.020000  14.420000 ( 14.560012)
       14.56 real        14.40 user         0.02 sys

### Ruby Enterprise Edition 1.8.7
$ ruby -v
ruby 1.8.7 (2009-12-24 patchlevel 248) [i686-darwin10.3.0], MBARI 0x6770, Ruby Enterprise Edition 2010.01
$ /usr/bin/time ruby test1.rb
                user     system      total        real
1.8.7      12.610000   0.010000  12.620000 ( 12.839836)
       12.84 real        12.61 user         0.01 sys

### Ruby 1.9.1
$ ruby -v
ruby 1.9.1p378 (2010-01-10 revision 26273) [i386-darwin10.2.0]
$ /usr/bin/time ruby test1.rb
                user     system      total        real
1.9.1       2.040000   0.000000   2.040000 (  2.125471)
        2.14 real         2.05 user         0.00 sys

### Rubinius 1.0.0
$ rbx -v
rubinius 1.0.0 (1.8.7 release 2010-05-14 JI) [x86_64-apple-darwin10.3.0]
$ /usr/bin/time rbx test1.rb
                user     system      total        real
1.8.7       1.108981   0.000000   1.108981 (  1.108993)
        1.53 real         1.53 user         0.07 sys


速い順でいうと、「Rubinius > Ruby 1.9 >>> REE 1.8.7 > Ruby 1.8.7」という結果に。なんと Rubinius は Ruby 1.9 より速いぞ! (フィボナッチ的に)。

なおこの結果をみると、Rubinius の起動時間が 1.53 real - 1.108993 = 約 0.4 秒程度ということがわかる。以前よりも改善されてはいるけど、Ruby 1.8.7 や 1.9.1 が瞬間で起動するのと比べると、ちょっと見劣りがする。やっぱり CGI 用途ではきついかな*1

ベンチマークその2: 文字列処理 (HTML 生成)

フィボナッチ数列のような「実験室用のプログラム」ではなく、もっと現実感のあるベンチマークとして、HTML を生成するプログラムを実行してみた。

ここでも Rubinius が驚くべき結果に!


test2.rb

items = [
  { 'id'=>1001, 'name'=>'AAA' },
  { 'id'=>1002, 'name'=>'BBB' },
  { 'id'=>1003, 'name'=>'CCC' },
  { 'id'=>1004, 'name'=>'DDD' },
  { 'id'=>1005, 'name'=>'EEE' },
  { 'id'=>1006, 'name'=>'FFF' },
  { 'id'=>1007, 'name'=>'GGG' },
  { 'id'=>1008, 'name'=>'HHH' },
  { 'id'=>1009, 'name'=>'III' },
  { 'id'=>1010, 'name'=>'JJJ' },
]

def gen_html(n, items)
  buf = nil
  n.times do
    buf = ''
    buf << <<END
<html>
  <body>
   <table>
     <thead>
       <tr>
         <th>id</th><th>name</th>
       </tr>
     </thead>
     <tbody>
END
    is_odd = false
    for item in items
      is_odd = !is_odd
      buf << "       <tr class=\"#{is_odd ? 'odd' : 'even'}\">
         <td>#{item['id']}</td><td>#{item['name']}</td>
       </tr>
"
    end
    buf << <<END
     </tbody>
   </table>
  </body>
</html>
END
  end
  buf
end

require 'benchmark'
html = nil
n = ($n || 100000).to_i
Benchmark.bm(10) do |x|
  x.report(RUBY_VERSION) { html = gen_html(n, items) }
end

#print html
#puts "Enter to exit"
#$stdin.read


実行結果

### Ruby 1.8.7
$ /usr/bin/time ruby test2.rb
                user     system      total        real
1.8.7       3.970000   0.000000   3.970000 (  4.014548)
        4.02 real         3.97 user         0.00 sys

### Ruby Enterprise Edition 1.8.7
$ /usr/bin/time ruby test2.rb
                user     system      total        real
1.8.7       3.910000   0.000000   3.910000 (  3.948740)
        3.95 real         3.91 user         0.00 sys

### Ruby 1.9.1
$ /usr/bin/time ruby test2.rb
                user     system      total        real
1.9.1       4.640000   0.010000   4.650000 (  4.680164)
        4.69 real         4.65 user         0.01 sys

### Rubinius 1.0.0
$ /usr/bin/time rbx test2.rb
                user     system      total        real
1.8.7       3.334340   0.000000   3.334340 (  3.334336)
        3.75 real         3.79 user         0.08 sys


なんとここでも Rubinius が最速に! 速い順で「Rubinius > REE 1.8.7 > Ruby 1.8.7 > Ruby 1.9.1」という結果になった。
Rubinius の文字列処理は、2年前は泣きたくなるくらい遅かったんだけど、1.0.0 では大幅に高速化されていた。

Rubinius の作者である Evan Phoenix のコメント:

我々は、 Rubiniusでは、string(文字列オブジェクト)が遅くなる傾向にあると聞いた、そして大抵のRubyのアプリにおいて、stringは、大きな部分を占めるので、現在、Rubiniusを使ってどの程度期待できで、将来、この問題は、どのように処理されるのかを知りたかった:

全てのstringが1.8.xより遅い、と言うのは正確でありません。Stringメソッドの多くは、1.8と同じか速いです。現在のところ、僅かなものだけが、遅い、例えば、tr, gsub, unpackなどです。我々はこれらをより高速にするために鋭意開発中です。

Rubiniusが1.0になった

ということで、文字列処理はきちんと高速化されてました。すばらしい。

結論

「Rubinius が速い」というのは、ウソ偽りのない本当のことでした。

Rubinius が高速なのは、LLVMJIT のおかげ。今回の結果や、LuaJIT が異常に高速なことや、PyPy が CPython より高速なことを考えると、今後は「どれだけ優秀な JIT を搭載しているか」がプログラミング言語高速化のカギを握るのだろう。

TODO: メモリ使用量の測定

あわせてよみたい:

*1:でも Rubinius はスクリプトを事前にコンパイルする機能があるから、スクリプトのロード時間は短縮できるはず。