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 に移れということかなあ。