ruby の log は遅い
タイトルはちょっと釣りで、ruby を dis るのが目的ではない。
今書いてるコードで、log が入る計算がやたら遅いので、RubyInline で C の呼び出しにしたらだいぶ速くなった。これはちゃんと計測しなくてはということで、書いたのがこちら。
require 'benchmark' require 'inline' class Test inline do |builder| builder.include('<math.h>') builder.c <<-EOF double log_c(int i) { return log(i); } EOF end end Benchmark.bmbm do |x| t = Test.new rands = [] 10_000_000.times do rands << rand(1000) end x.report('ruby') do rands.each do |int| Math.log(int) end end x.report('inline') do rands.each do |int| t.log_c(int) end end end # Rehearsal ------------------------------------------ # ruby 9.840000 0.000000 9.840000 ( 9.842299) # inline 2.650000 0.010000 2.660000 ( 2.667835) # -------------------------------- total: 12.500000sec # user system total real # ruby 10.410000 0.010000 10.420000 ( 10.437814) # inline 2.420000 0.000000 2.420000 ( 2.422468)
ただlogの計算をするだけなのに、5倍くらい速度がちがう。
最終的な実装はどっちにしろ "math.h" の log だろうと思うのに、大変不思議。
ruby の Math モジュールのソースコードを読んでみる
static VALUE math_log(int argc, VALUE *argv) { VALUE x, base; double d0, d; rb_scan_args(argc, argv, "11", &x, &base); Need_Float(x); d0 = RFLOAT_VALUE(x); /* check for domain error */ if (d0 < 0.0) domain_error("log"); /* check for pole error */ if (d0 == 0.0) return DBL2NUM(-INFINITY); d = log(d0); if (argc == 2) { Need_Float(base); d /= log(RFLOAT_VALUE(base)); } return DBL2NUM(d); }
おお。不正な引数のために、いろいろ頑張ってる。
これのどこが 5 倍も遅くしてるのか調べたい気もするけれど、今日はここまでにしておこう。
ちなみに、RubyInline で作った log 関数に -1 や 0 を与えると、それぞれ、NaN と -Infinity が返ってくるので、わりと実用に困らない。Math モジュールも工夫の余地があるかも知れない。
最初 Gist に書いたんだけど、埋め込み用のJSがエスケープされてしまう... blog に移れということかなあ。