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を使ってどの程度期待できで、将来、この問題は、どのように処理されるのかを知りたかった:
Rubiniusが1.0になった全てのstringが1.8.xより遅い、と言うのは正確でありません。Stringメソッドの多くは、1.8と同じか速いです。現在のところ、僅かなものだけが、遅い、例えば、tr, gsub, unpackなどです。我々はこれらをより高速にするために鋭意開発中です。
ということで、文字列処理はきちんと高速化されてました。すばらしい。
結論
「Rubinius が速い」というのは、ウソ偽りのない本当のことでした。
Rubinius が高速なのは、LLVM や JIT のおかげ。今回の結果や、LuaJIT が異常に高速なことや、PyPy が CPython より高速なことを考えると、今後は「どれだけ優秀な JIT を搭載しているか」がプログラミング言語高速化のカギを握るのだろう。
TODO: メモリ使用量の測定
あわせてよみたい: