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: メモリ使用量の測定
あわせてよみたい: