かってにインパクトファクター

子育てサラリーマンが日々の雑多なことをつらつらと綴ってます。時々政治ネタ経済ネタコンピュータネタなどをはさみます。

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

ruby 1.9

$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

jruby 1.7
(jrubyは2回目の実行時間を表示)

$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オプションをつけて実行してみましたが、実行結果はそこまで差がありませんでした。
グラフも載せておきます。
f:id:postmaster:20150621181911p:plain
f:id:postmaster:20150622070303p:plain


ついでに、rubyとどれくらい近いのか見るのに、変数の型の自由度を見てみました。

a = 10
puts a
a = "Hello, world!"
puts a

結果は

$crystal run syntax_test.cr
10
Hello, world!

となっていて、同じ変数に違う型をそのまま入れることは出来ないようです。ただ、エラーが非常にわかりやすいのは良いですね。
asteriteさんからコメントを頂いて確認したところ、ソースが間違えていました。修正したところ、無事に数値を代入した後に文字列を代入できました。
Crystalすごいですね。これでRubyを包括した言語に育ってくれれば、私の要求をほぼすべて満たすんではないでしょうか?