Ruby風の文法でかけるcrystalが面白そうだったので、ベンチマークしてみた
私は基本的にプログラムを作成するときにはRubyを選択することが多いです。なんせ簡単にかけますから。
ただ、個人的にRubyは書いてて楽しいんですが、ちょっぴり不満も有ります。
それは、実行速度が遅いこと。
と言っても、実用上ほとんど問題になることはありません。
なんせ、実行速度のボトルネックになるI/O周りなどは、言語が早くなってもそれほど差がないからです。
そのため、RubyがCとくらべて50倍遅くても、プログラム本体が50倍遅くなることはまずありません。
(今回のベンチマークの結果を見ると、10倍程度遅いという位が正確かもしれませんが。)
そうなんです。私の作っているプログラムでもほとんど問題にならないんですが、ごく一部だけとっても問題になるところがあるんです。
それは、データを全部配列に突っ込んだあとで何度も何度も条件を変更しながら最適値を探すシミュレーション部分。
だったらそこは別の言語でかけよって言われそうですが、Rubyの便利な機能も一部使っています。
RubyからCを呼び出すのも若干面倒な上、開発環境がWindowsでプロダクション環境がFreeBSDというのもあって断念。今のところJRubyが候補としては有りなのかなとも思えるんですが、もっと便利に簡単に行かないものかしら、ということに興味があって、ネットを時々あさります。
そんなわけで先日もruby周りでおもしろことないかなーと見ていたら、なんとruby風の文法でバイナリを生成してくれるCrystalなるものを見つけました。
Rubyベースの文法で高速に動作するNativeコードにコンパイルされるCrystal言語の情報まとめ #crystal #ruby - Tbpgr Blog
で、せっかくだから試してみるかと、サクラVPS上のFreeBSDでインストールを試みたんですが、何をやってもうまく行かず。
ということで、CentOSを入れて試してみました(ちょうど、サーバを移行しているタイミングだったので、移行元サーバは潰しても大丈夫な状態だったんです)。
インストール方法などは他を参照していただくとして、ベンチマークを取ってみたんで結果を載せます。
Rubyのソースは
フィボナッチで各種言語をベンチマーク - satosystemsの日記
モンテカルロ数値積分法による単位円の面積をRubyで - なぜか数学者にはワイン好きが多い
モンテカルロ法の数値計算例(C言語)
辺りから借用しました(一部変更していますが)。
fib.rb
def fib(n) return n if (n < 2) return fib(n - 2) + fib(n - 1) end puts fib(38)
fib.cr
これはRubyと同じソースが実行出来ました。
def fib(n) return n if (n < 2) return fib(n - 2) + fib(n - 1) end puts fib(38)
monte.rb
j = 1 5_000_000.times{|i| s1 = rand() s2 = rand() if Math::sqrt(s1**2 + s2**2) <= 1.0 then j += 1 end } puts 4.0 * j / 5_000_000
monte.cr
Math::sqrtが無いので、Cを呼び出すんですかね?作者のWebサイトに有る書き方を拾いました。
あと、if文ではthenを入れるとエラーになります。
j = 1 lib C fun sqrt(value : Float64) : Float64 end 5_000_000.times{|i| s1 = rand() s2 = rand() if C.sqrt(s1**2 + s2**2) <= 1.0 j += 1 end } puts 4.0 * j / 5_000_000
fib.c
#include <stdio.h> int fib(int n){ if (n < 2) return n; return fib(n - 2) * fib(n - 1); } int main(void){ printf("%d\n", fib(38)); return 0; }
monte.c
#include <stdio.h> #include <stdlib.h> int main(void){ double i,j,imax; double s1,s2; j = 0.0; imax = 5000000.0; for (i = 0; i <= imax ; i++){ j += 1.0; } printf("%f\n",j); return 0; }
ruby 1.8の結果から順番に載せていきます。
$ruby -v ruby 1.8.7 (2013-06-27 patchlevel 374) [x86_64-linux] $time ruby fib.rb 39088169 real 2m28.720s user 2m8.049s sys 0m20.626s $time ruby monte.rb 3.1411400 real 0m18.552s user 0m17.308s sys 0m1.233s
$rbenv local 1.9.3-p551 $ruby -v ruby 1.9.3p551 (2014-11-13 revision 48407) [x86_64-linux] $time ruby fib.rb 39088169 real 0m30.593s user 0m21.377s sys 0m0.058s $time ruby monte.rb 3.1411888 real 0m6.833s user 0m6.281s sys 0m0.080s
ruby 2.0
$rbenv local 2.0.0-p645 $ruby -v ruby 2.0.0p645 (2015-04-13 revision 50299) [x86_64-linux] $time ruby fib.rb 39088169 real 0m25.073s user 0m15.362s sys 0m0.057s $time ruby monte.rb 3.1405352 real 0m5.509s user 0m4.807s sys 0m0.110s
ruby 2.1
$rbenv local 2.1.6 $ruby -v ruby 2.1.6p336 (2015-04-13 revision 50298) [x86_64-linux] $time ruby fib.rb 39088169 real 0m22.501s user 0m22.408s sys 0m0.081s $time ruby monte.rb 3.1403808 real 0m3.527s user 0m3.481s sys 0m0.035s
ruby 2.2
$rbenv local 2.2.2 $ruby -v ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux] $time ruby fib 39088169 real 0m30.853s user 0m20.781s sys 0m0.061s $time ruby monte.rb 3.1408192 real 0m3.534s user 0m3.498s sys 0m0.033s
$rbenv local jruby-1.7.20.1 $ruby -v jruby 1.7.20.1 (1.9.3p551) 2015-06-10 d7c8c27 on OpenJDK 64-Bit Server VM 1.7.0_79-mockbuild_2015_05_14_07_23-b00 +jit [linux-amd64] $time ruby fib 39088169 real 0m13.005s user 0m17.860s sys 0m0.576s $time ruby monte.rb 3.140864 real 0m16.677s user 0m23.103s sys 0m0.949s
jruby 9.0
$rbenv local jruby-9.0.0.0.rc1 $ruby -v jruby 9.0.0.0.rc1 (2.2.2) 2015-06-10 a0bf3b3 OpenJDK 64-Bit Server VM 24.79-b02 on 1.7.0_79-mockbuild_2015_05_14_07_23-b00 +jit [linux-amd64] $time ruby fib.rb $time ruby fib 39088169 real 0m19.494s user 0m24.343s sys 0m0.662s $time ruby monte.rb 3.1401752 real 0m12.704s user 0m16.983s sys 0m0.656s
そして、今日の本題、crystalの結果です。
$crystal --version Crystal 0.7.3 [bf72b07] (Sun Jun 7 16:33:22 UTC 2015) $time crystal run fib.cr 39088169 real 0m1.842s user 0m1.450s sys 0m0.216s $time crystal build fib.cr real 0m1.011s user 0m0.389s sys 0m0.247s $time ./fib 39088169 real 0m0.926s user 0m0.794s sys 0m0.003s $time crystal run monte.cr 3.14147 real 0m2.558s user 0m2.357s sys 0m0.346s $time crystal build monte.cr real 0m0.467s user 0m0.299s sys 0m0.291s $time ./monte 3.14214 real 0m1.902s user 0m1.898s sys 0m0.004s
参考までにCの速度も測ってみました。
$gcc fib.c $time ./a.out 39088169 real 0m1.078s user 0m1.065s sys 0m0.005s $gcc monte.c $time ./a.out 3.140899 real 0m0.332s user 0m0.325s sys 0m0.004s
asteriteさんからコメントを頂いて、オプションをつけてビルドしてねということでしたので、その結果も載せておきます。
$crystal build monte.cr --release $time ./monte 3.14198 real 0m0.312s user 0m0.307s sys 0m0.004s
結果としては、fibもmonteもC言語とほぼ変わらない速度。monteは最新のrubyとそれほど差がありません。バイナリの大きさはC言語をコンパイルしたものと比べるト100倍程度大きいのですが、ソースが小さいのでもっと大きなソースになればそれほど差は気にならないかもしれません。
ちなみに、asteriteさんからコメントを頂いた後、fibも--releaseオプションをつけて実行してみましたが、実行結果はそこまで差がありませんでした。
グラフも載せておきます。
ついでに、rubyとどれくらい近いのか見るのに、変数の型の自由度を見てみました。
a = 10 puts a a = "Hello, world!" puts a
結果は
$crystal run syntax_test.cr 10 Hello, world!
となっていて、同じ変数に違う型をそのまま入れることは出来ないようです。ただ、エラーが非常にわかりやすいのは良いですね。
asteriteさんからコメントを頂いて確認したところ、ソースが間違えていました。修正したところ、無事に数値を代入した後に文字列を代入できました。
Crystalすごいですね。これでRubyを包括した言語に育ってくれれば、私の要求をほぼすべて満たすんではないでしょうか?