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に修正しました。