simanのブログ

ゆるふわプログラマー。競技プログラミングやってます。Ruby好き

RubyのIntegerにおけるsuccとnext

自分はRubyで数値をインクリメントするときはいつも「+=1」を使用しているのですが、succメソッドとnextメソッドもインクリメントに使用できるので、どれが一番速いのか調べてみました。

require 'benchmark'
 
Benchmark.bm do |x|
  cnt = 100000000
  fixnum = 1
  bignum = 1 << 64
 
 
  x.report("+=1 Fixnum") do
    a = fixnum
    cnt.times{ a += 1 }
  end
 
  # Fixnum#succ
  x.report("Fixnum succ") do 
    a = fixnum
    cnt.times{ a = a.succ }
  end
 
  # Fixnum#next
  x.report("Fixnum next") do 
    a = fixnum
    cnt.times{ a = a.next }
  end
 
  x.report("+=1 Bignum") do
    a = bignum
    cnt.times{ a += 1 }
  end
 
  # Bignum#succ
  x.report("Bignum succ") do 
    a = bignum
    cnt.times{ a = a.succ }
  end
 
  # Bignum#next
  x.report("Bignum_next") do 
    a = bignum
    cnt.times{ a = a.next }
  end
end

実行結果

       user     system      total        real
+=1 Fixnum  5.550000   0.010000   5.560000 (  5.607988)
Fixnum succ  5.100000   0.000000   5.100000 (  5.104199)
Fixnum next  7.200000   0.000000   7.200000 (  7.241638)
+=1 Bignum 11.610000   0.010000  11.620000 ( 11.641881)
Bignum succ 11.960000   0.030000  11.990000 ( 12.039480)
Bignum_next 11.330000   0.010000  11.340000 ( 11.352631)

結果だけ見てみると、Bignumの時はどれもほとんど差がありませんが、Fixnumだとnextだけ遅い結果が出ました。

そこでnumeric.cの中のsuccの実装を見てみると

  VALUE
rb_int_succ(VALUE num)
{
  if (FIXNUM_P(num)) {
    long i = FIX2LONG(num) + 1;
    return LONG2NUM(i);
  }
  if (RB_TYPE_P(num, T_BIGNUM)) {
    return rb_big_plus(num, INT2FIX(1));
  }
  return rb_funcall(num, '+', 1, INT2FIX(1));
}

こんな感じになっていたのですが、別でfix_succなるものがありました。

  static VALUE
fix_succ(VALUE num)
{
  long i = FIX2LONG(num) + 1;
  return LONG2NUM(i);
}

そしてメソッド定義の部分を見ると

  rb_define_method(rb_cInteger, "succ", int_succ, 0);
  rb_define_method(rb_cInteger, "next", int_succ, 0);
  rb_define_method(rb_cFixnum, "succ", fix_succ, 0);

こんな感じでsuccは2つ定義されていました。

つまりは、succメソッドはFixnumかBignumの違いで呼ばれていた関数が違っていたのではないでしょうか。rb_int_succはfix_succに比べ判定式があるので、その分Fixnumにおいてnextが遅くなったのかもしれません。

まだしっかりと調査しきれていませんが、結論としては「+=1」でいいやって感じです。


追記(2014/04/30)

記事のタイトルをNumericからIntegerに修正しました。