Ruby1.9 や Rubinius は MRI と比べて本当に速いのか
Ruby1.9 や Rubinius は MRI と比べて本当に速いのかどうか、確かめてみた。
結論からいうと、fibonacci sequence のような「ベンチマーク向き」のテストでは確かに速くなっている。しかし、文字列操作を多用するような実アプリケーションに近づくほど、速くはならないか、逆に遅くなる。
まず最初のテストスクリプト。
test1.rb: (fibonacci sequence)
def f(n) if n <= 1 return 1 else return f(n-1) + f(n-2) end end puts f(34)
time コマンドで計測してみる。Rubinius は起動が遅いので 0.5 秒くらい割り引いて考える必要があるが、ここでは無視する。
$ time ruby test1.rb 9227465 real 0m27.373s user 0m27.002s sys 0m0.071s $ time ruby1.9 test1.rb 9227465 real 0m5.911s user 0m5.863s sys 0m0.023s $ time rubinius test1.rb 9227465 real 0m10.434s user 0m10.244s sys 0m0.130s
これをみると、たしかに Ruby1.9 や Rubinius は Ruby1.8 (MRI) と比べて高速化しているように見える。
しかし、fibonacci sequence が速くなったからといって、実アプリケーションも速くなるとは限らない。そこで、文字列結合を多用する別のベンチマークスクリプトを用意した。このスクリプトは、Web application を意識している。
test2.rb: (string concatenation)
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' }, ] n = 100000 result = 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 result = buf end #print result
これを実行してみると、Ruby1.8 (MRI) が最も速く、Ruby1.9 (YARV) と Rubinius は遅くなっていることがわかる。特に Rubinius は Ruby1.8 と比べて 3 倍近く遅い。
$ time ruby test2.rb real 0m8.229s user 0m8.169s sys 0m0.027s $ time ruby1.9 test2.rb real 0m11.101s user 0m11.017s sys 0m0.045s $ time rubinius test2.rb real 0m22.160s user 0m21.774s sys 0m0.203s
Ruby1.9 が遅いのは、たぶん YARV のせいではなく、String が M18N になったことが関連していると思われる。また Rubinius が遅いのは、String の動作の tuning がまだされていないせい (参考: ticket #359 [TASK] Improve performance of String)。
もうひとつ、Erubis 付属のベンチマークを実行してみた。以下がベンチマーク結果である (ただし必要なとこだけを抜粋)。
$ ruby bench.rb -n 1000 *** ntimes=1000, testmode=execute user system total real ERB 6.670000 0.070000 6.740000 ( 6.781463) ERB(cached) 2.160000 0.090000 2.250000 ( 2.263453) Erubis::Eruby 1.950000 0.050000 2.000000 ( 2.018663) Erubis::Eruby(cached) 1.360000 0.080000 1.440000 ( 1.444246) Erubis::TinyEruby 1.720000 0.060000 1.780000 ( 1.780438) $ ruby1.9 bench.rb -n 1000 *** ntimes=1000, testmode=execute user system total real ERB 6.880000 0.130000 7.010000 ( 7.035035) ERB(cached) 2.860000 0.140000 3.000000 ( 3.005437) Erubis::Eruby 2.210000 0.100000 2.310000 ( 2.326651) Erubis::Eruby(cached) 1.590000 0.130000 1.720000 ( 1.719405) Erubis::TinyEruby 2.050000 0.100000 2.150000 ( 2.157506) $ rubinius bench.rb -n 1000 *** ntimes=1000, testmode=execute user system total real ERB 189.277537 0.000000 189.277537 (189.277537) ERB(cached) 120.888135 0.000000 120.888135 (120.888123) Erubis::Eruby 60.735775 0.000000 60.735775 ( 60.735763) Erubis::Eruby(cached) 59.077564 0.000000 59.077564 ( 59.077554) Erubis::TinyEruby 63.876757 0.000000 63.876757 ( 63.876746)
また Ruby1.9 も、Ruby1.8 より遅くなっている。eRuby ではどうしても eval() が絡むからしかたないんだけど、とにかく Ruby1.9 になればなんでもかんでも速くなるというのは間違いであると断言できる。
eval 抜きだとどうなるのか。Erubis のベンチマークスクリプトでは、eval での実行をせず、eRuby ファイルのコンパイルだけを実行することができるので、それで調べてみた。
$ ruby bench.rb -n 1000 -m convert *** ntimes=1000, testmode=convert user system total real ERB 4.610000 0.060000 4.670000 ( 4.733398) Erubis::Eruby 0.680000 0.060000 0.740000 ( 0.735850) Erubis::TinyEruby 0.440000 0.050000 0.490000 ( 0.491361) $ ruby1.9 bench.rb -n 1000 -m convert *** ntimes=1000, testmode=convert user system total real ERB 4.160000 0.100000 4.260000 ( 4.291514) Erubis::Eruby 0.720000 0.090000 0.810000 ( 0.820965) Erubis::TinyEruby 0.550000 0.080000 0.630000 ( 0.635659) $ rubinius bench.rb -n 1000 -m convert *** ntimes=1000, testmode=convert user system total real ERB 71.645604 0.000000 71.645604 ( 71.645608) Erubis::Eruby 3.654989 0.000000 3.654989 ( 3.654977) Erubis::TinyEruby 3.444701 0.000000 3.444701 ( 3.444691)
絶望した! eval ないのにひとケタ遅い Rubinius に絶望した!
また eval がなくても、Ruby1.9 は Ruby1.8 と大して変わらないことがわかる。
結局、Ruby1.9 や Rubinius で速くなるのはバイトコードの実行部分だけであり、String#<< のような built-in method の実行が速くなるわけではない。fibonacchi sequence のベンチマークが速いのは built-in method の呼び出しがない、純粋にバイトコードの実行だけで済むベンチマークだからである。
逆に、String#<< のような built-in method の実行時間が増えれば増えるほど YARV の高速性が生かされないので Ruby1.9 でも速さは変わらなくなるし、Rubinius のように built-in method が遅い場合はベンチマークの結果も Ruby1.8 より遅くなる。
結論: Ruby1.9 にしたからといって速くなるとは限らない。速くなればラッキー、ぐらいに考えていたほうがよさそう。