しつこく JRuby
Mac の JDK は性能が出ないってことかなあ?
先方へのコメントには test/fib.rb って書いたんですが、 samples には fib.rb というファイルは(私のアーカイブには)なかったのでカンチガイでした。とまれ、この test/fib.rb というプログラムを実行させてみました。その前に簡単に説明すると、 test/fib.rb では、再帰ではなく逐次的計算でフィボナッチ数を計算しています。でも通常のループではなく末尾再帰的に計算しているため、そのうちスタックが溢れてしまうと。じゃ、どれくらいのデカい引数ならスタックが溢れますか、というのを SystemStackError で検知して、二分探索的に調べるとまあ、そういうプログラムのようです。
% bin/jruby test/fib.rb Estimating max fib recursion. This will be slightly lower than actual. good: 1 good: 2 good: 4 (snip) bad: 14849 17.675000 0.000000 17.675000 ( 17.617000) % ruby test/fib.rb Estimating max fib recursion. This will be slightly lower than actual. good: 1 good: 2 good: 4 (snip) good: 6391 0.370000 0.020000 0.390000 ( 0.397956) % /usr/local/ruby-1.9/bin/ruby19 test/fib.rb Estimating max fib recursion. This will be slightly lower than actual. good: 1 good: 2 good: 4 (snip) good: 6225 0.160000 0.010000 0.170000 ( 0.166586)
ガーン、スタックの大きさが違う。
この fib.rb というのは、フィボナッチ数を(再帰的ではなく繰り返しによって)計算しますが、再帰しているので巨大な場合はスタックを食い潰す、ということでどれくらい深く再帰できるかを二分探索的に調べている方式です。あれっ、 ruby1.9 って末尾再帰の最適化をしていないのかな?
まあいいや。
そういうわけでやっぱり JRuby が猛烈に遅いのですね。 JDK のせいなのかなあ。
ほかにも test/bench の下にフィボナッチ数の計算を逐次的にやる版や再帰的にやる版のベンチマークもありました。これも JRuby の方が若干遅いのですが、面白いことに逐次的計算の場合、 ruby 1.8.6 と ruby 1.9.0 は大差ない性能になっていました。
ところで前回、結論めいたことを書こうかどうしようか迷ったのですが、そうしているうちに keisuken さんに書かれてしまいました。実はわたしも、パフォーマンスには大した問題はないと思っています。いや、少し違うかな、速いならそれにこしたことはないのですが、ほかにも重要なことっていっぱいあるでしょう。というか、 Ruby は(Rubyistは)これまで散々遅い遅いと揶揄されてきていて、そこで「それよりももっと重要なことがある」という開き直りをしてきたわけです。自分より遅い処理系が出てきたからといって態度を変えるべきではない。
JRuby には JRuby のメリットがあるわけで、きちんとそういう点を評価をするべきではないかなーと思います。べつに遅くったっていいでしょう。でも国内のメディアでは「本家Rubyよりも速い」という触れ込みが効いている気がするんですよね。上みたいな極端な速度差はともかく、実際には大差ないんじゃないかと思うのだけど、どうなのかなあと。
ところで、マイコミジャーナルのOpenOffice のマクロを groovy で書くという記事を読んだら「誰か JRuby でもやってくんないかなー」と思ったり(他力本願)。
jrubyが速いのはバイトコードにコンパイル後らしいですよ。
http://ko.meadowy.net/~koichiro/diary/20070518.html#p01
(sigh)
知ってますよ。ま、上で言及した test/fib.rb は Not compileable なので比較はできませんが(rescueがあるのもダメらしい)。
それから念のために書いておきますが、上の結果では ruby 1.8.6 は jruby の50倍くらい速いわけです。バイトコンパイルでそれだけ速くなるか、怪しいもんだと思いますがね。わたしが試した感じでは、そこまで高速化していないようですが、あさんはどんなコードで試しましたか?
それから、上に「先方」というところでリンクした keisuken さんの日記では、ちゃんと JRuby の方が速い場合があることが示せていますが、あさんはそれを読んでいますか?
>> あれっ、 ruby1.9 って末尾再帰の最適化をしていないのかな?
互換性 (昔の ruby だとスタックが溢れるコードが正常に動くようになるから) とか,デバグが難しくなる (スタックトレースができない) とかの理由で,オプションになっていたような.あと,単にスタックを溢れないようにしただけで,高速化には貢献してなかったような ? (ソース失念すいません)
そうなんでしたか。失礼しました。
わかりにくくてすいませんが、その発言だけ速度は関係ないです。このプログラムは(逐次的なアルゴリズムの)フィボナッチ数を再帰的に計算していて、スタック溢れを検出しているんですよ。末尾再帰最適化がされているなら、たぶんまったく失敗が発生しなくて、いつまでも終了しないプログラムになる(まさに互換性の問題が起こる)はずなので「あれっ?」と思ったわけです。
ちょっとコードを見てみたんですが、現在は COMPILE_OPTION_DEFAULT が使われているようなので、コマンドラインオプションとかで挙動を変更したりはできないようですね。コンパイルしなおしてみましたが、末尾再帰最適化版 ruby-1.9 はちゃんと止まらなくなりました :-)
ちなみに、わたしが試したコードは下記のURLで閲覧できます。
http://svn.jruby.codehaus.org/browse/~raw,r=2262/jruby/trunk/jruby/test/fib.rb
Mac の Java はデフォルトで Client VM なようなので、それが原因じゃないでしょうか。
export JAVA_OPTS=’-server’
として Server VM を使うようにすればかなり違うかもしれません。
試してみました。パフォーマンスはかなり改善されますね(ちゃんと見てみたらkeisukenさんもやっていますね。-J-server でもいいらしい)。それでも私の環境では ruby 1.8.6 よりけっこう遅いですが、今後は ruby より速くなるのかも、と言われれば納得できるくらいです。
ところで、上で「50倍」といったのは公正ではないので訂正します。スタックサイズの違いから試行回数が変わりますので。同じ数値でエラーをわざと raise するという胡散臭い方法では、通常で 4.8秒、 -J-server で 1.7秒となったので、高々5〜10倍といったところでした。すいません。