斎藤と申します。

だいぶ古い話なのですが、[ruby-core:5066]で指摘された問題に対する
修正をより広範囲に適用して、Fixnumの数値演算メソッドの引数に
BignumとFloatどちらが来てもcoerceしないようにしてみました。
コード量が増えることと2つほど関数をexportしなければならないのは
難ですが、手元で試した限り当該ケースでは平均1.8倍、メソッドに
よっては2.7倍程度高速化するようです。以下、ログを載せます。

適用前:

$ cat benchmark_fixnum.rb
def measure(message)
  t = Time.now
  yield
  d = Time.now - t
  puts "Fixnum #{message}: #{d} sec"
end

fix = 2
big = 0x100000000
flo = 2.0
N = 1000000
measure("+ Bignum") {N.times {fix + big}}
measure("- Bignum") {N.times {fix - big}}
measure("* Bignum") {N.times {fix * big}}
measure("quo Bignum") {N.times {fix.quo(big)}}
measure("quo Float") {N.times {fix.quo(flo)}}
measure("/ Bignum") {N.times {fix / big}}
measure("/ Float") {N.times {fix / flo}}
measure("% Bignum") {N.times {fix % big}}
measure("% Float") {N.times {fix % flo}}
measure("divmod Bignum") {N.times {fix.divmod(big)}}
measure("divmod Float") {N.times {fix.divmod(flo)}}
measure("** Bignum") {N.times {fix ** big}}
$ ../miniruby benchmark_fixnum.rb 2>/dev/null >before
$ cat before
Fixnum + Bignum: 3.098961 sec
Fixnum - Bignum: 3.157194 sec
Fixnum * Bignum: 3.233673 sec
Fixnum quo Bignum: 3.191216 sec
Fixnum quo Float: 3.040647 sec
Fixnum / Bignum: 3.220508 sec
Fixnum / Float: 2.824229 sec
Fixnum % Bignum: 2.747188 sec
Fixnum % Float: 2.831133 sec
Fixnum divmod Bignum: 3.746982 sec
Fixnum divmod Float: 3.637524 sec
Fixnum ** Bignum: 22.421713 sec


適用後:

$  ../miniruby benchmark_fixnum.rb 2>/dev/null >after
$ cat after
Fixnum + Bignum: 1.845635 sec
Fixnum - Bignum: 2.002788 sec
Fixnum * Bignum: 1.927141 sec
Fixnum quo Bignum: 1.207432 sec
Fixnum quo Float: 1.122165 sec
Fixnum / Bignum: 2.059681 sec
Fixnum / Float: 2.05763 sec
Fixnum % Bignum: 1.54422 sec
Fixnum % Float: 1.177173 sec
Fixnum divmod Bignum: 2.575647 sec
Fixnum divmod Float: 2.072219 sec
Fixnum ** Bignum: 21.025464 sec
$ cat cmp.rb
raise "usage: #{$0} <before> <after>" unless ARGV.size == 2
 
puts '[method & arg]: [n times faster]'
before = open(ARGV[0]) {|f| f.readlines }
after = open(ARGV[1]) {|f| f.readlines }
raise 'internal error' unless before.size == after.size
assoc = before.zip(after)
sum = 0.0
assoc.each do |before, after|
  d = before.split[-2].to_f / after.split[-2].to_f
  puts "#{before.split[0..2].join(' ')} #{d}"
  sum += d
end
puts "<average: #{sum/assoc.size}>"
$ ruby cmp.rb before after
[method & arg]: [n times faster]
Fixnum + Bignum: 1.67907576525153
Fixnum - Bignum: 1.57639949909826
Fixnum * Bignum: 1.67796388536179
Fixnum quo Bignum: 2.64297782400996
Fixnum quo Float: 2.70962558981968
Fixnum / Bignum: 1.56359552765695
Fixnum / Float: 1.37256406642594
Fixnum % Bignum: 1.77901335301965
Fixnum % Float: 2.4050271285529
Fixnum divmod Bignum: 1.45477311137745
Fixnum divmod Float: 1.75537624160381
Fixnum ** Bignum: 1.0664075237531
<average: 1.80689995966092>


いかがでしょうか。もしパッチを取りこんでいただけるなら、引き続いて
比較演算についても同様のものを作りたいと思います。
では、検討をお願いします。以下パッチです。


Index: bignum.c
===================================================================
RCS file: /src/ruby/bignum.c,v
retrieving revision 1.117
diff -u -p -r1.117 bignum.c
--- bignum.c	25 Jul 2005 07:08:13 -0000	1.117
+++ bignum.c	28 Jul 2005 13:23:12 -0000
@@ -1424,7 +1424,7 @@ bigdivmod(x, y, divp, modp)
  *  Divides big by other, returning the result.
  */
 
-static VALUE
+VALUE
 rb_big_div(x, y)
     VALUE x, y;
 {
@@ -1458,7 +1458,7 @@ rb_big_div(x, y)
  *  information.
  */
 
-static VALUE
+VALUE
 rb_big_modulo(x, y)
     VALUE x, y;
 {
Index: numeric.c
===================================================================
RCS file: /src/ruby/numeric.c,v
retrieving revision 1.121
diff -u -p -r1.121 numeric.c
--- numeric.c	25 Jul 2005 07:08:13 -0000	1.121
+++ numeric.c	28 Jul 2005 13:23:13 -0000
@@ -1985,10 +1985,14 @@ fix_plus(x, y)
 	}
 	return r;
     }
-    if (TYPE(y) == T_FLOAT) {
+    switch (TYPE(y)) {
+      case T_BIGNUM:
+	return rb_big_plus(y, x);
+      case T_FLOAT:
 	return rb_float_new((double)FIX2LONG(x) + RFLOAT(y)->value);
+      default:
+	return rb_num_coerce_bin(x, y);
     }
-    return rb_num_coerce_bin(x, y);
 }
 
 /*
@@ -2018,10 +2022,15 @@ fix_minus(x, y)
 	}
 	return r;
     }
-    if (TYPE(y) == T_FLOAT) {
+    switch (TYPE(y)) {
+      case T_BIGNUM:
+	x = rb_int2big(FIX2LONG(x));
+	return rb_big_minus(x, y);
+      case T_FLOAT:
 	return rb_float_new((double)FIX2LONG(x) - RFLOAT(y)->value);
+      default:
+	return rb_num_coerce_bin(x, y);
     }
-    return rb_num_coerce_bin(x, y);
 }
 
 /*
@@ -2053,10 +2062,14 @@ fix_mul(x, y)
 	}
 	return r;
     }
-    if (TYPE(y) == T_FLOAT) {
+    switch (TYPE(y)) {
+      case T_BIGNUM:
+	return rb_big_mul(y, x);
+      case T_FLOAT:
 	return rb_float_new((double)FIX2LONG(x) * RFLOAT(y)->value);
+      default:
+	return rb_num_coerce_bin(x, y);
     }
-    return rb_num_coerce_bin(x, y);
 }
 
 static void
@@ -2107,7 +2120,14 @@ fix_quo(x, y)
     if (FIXNUM_P(y)) {
 	return rb_float_new((double)FIX2LONG(x) / (double)FIX2LONG(y));
     }
-    return rb_num_coerce_bin(x, y);
+    switch (TYPE(y)) {
+      case T_BIGNUM:
+	return rb_float_new((double)FIX2LONG(y) / rb_big2dbl(y));
+      case T_FLOAT:
+	return rb_float_new((double)FIX2LONG(x) / RFLOAT(y)->value);
+      default:
+	return rb_num_coerce_bin(x, y);
+    }
 }
 
 /*
@@ -2130,7 +2150,15 @@ fix_div(x, y)
 	fixdivmod(FIX2LONG(x), FIX2LONG(y), &div, 0);
 	return LONG2NUM(div);
     }
-    return rb_num_coerce_bin(x, y);
+    switch (TYPE(y)) {
+      case T_BIGNUM:
+	x = rb_int2big(FIX2LONG(x));
+	return rb_big_div(x, y);
+      case T_FLOAT:
+	return rb_Integer(rb_float_new((double)FIX2LONG(x) / RFLOAT(y)->value));	
+      default:
+	return rb_num_coerce_bin(x, y);
+    }
 }
 
 /*
@@ -2152,7 +2180,20 @@ fix_mod(x, y)
 	fixdivmod(FIX2LONG(x), FIX2LONG(y), 0, &mod);
 	return LONG2NUM(mod);
     }
-    return rb_num_coerce_bin(x, y);
+    switch (TYPE(y)) {
+      case T_BIGNUM:
+	x = rb_int2big(FIX2LONG(x));
+	return rb_big_modulo(x, y);
+      case T_FLOAT:
+	{
+	    double mod;
+
+	    flodivmod((double)FIX2LONG(x), RFLOAT(y)->value, 0, &mod);
+	    return rb_float_new(mod);
+	}
+      default:
+	return rb_num_coerce_bin(x, y);
+    }
 }
 
 /*
@@ -2172,7 +2213,23 @@ fix_divmod(x, y)
 
 	return rb_assoc_new(LONG2NUM(div), LONG2NUM(mod));
     }
-    return rb_num_coerce_bin(x, y);
+    switch (TYPE(y)) {
+      case T_BIGNUM:
+	x = rb_int2big(FIX2LONG(x));
+	return rb_big_divmod(x, y);
+      case T_FLOAT:
+	{
+	    double div, mod;
+	    volatile VALUE a, b;
+
+	    flodivmod((double)FIX2LONG(x), RFLOAT(y)->value, &div, &mod);
+	    a = rb_float_new(div);
+	    b = rb_float_new(mod);
+	    return rb_assoc_new(a, b);
+	}
+      default:
+	return rb_num_coerce_bin(x, y);
+    }
 }
 
 /*
@@ -2202,11 +2259,16 @@ fix_pow(x, y)
 	    return rb_big_pow(rb_int2big(a), y);
 	}
 	return rb_float_new(pow((double)a, (double)b));
-    } else if (TYPE(y) == T_FLOAT) {
-        long a = FIX2LONG(x);
-        return rb_float_new(pow((double)a, RFLOAT(y)->value));
     }
-    return rb_num_coerce_bin(x, y);
+    switch (TYPE(y)) {
+      case T_BIGNUM:
+	x = rb_int2big(FIX2LONG(x));
+	return rb_big_pow(x, y);
+      case T_FLOAT:
+	return rb_float_new(pow((double)FIX2LONG(x), RFLOAT(y)->value));
+      default:
+	return rb_num_coerce_bin(x, y);
+    }
 }
 
 /*
Index: intern.h
===================================================================
RCS file: /src/ruby/intern.h,v
retrieving revision 1.174
diff -u -p -r1.174 intern.h
--- intern.h	23 Jul 2005 02:46:41 -0000	1.174
+++ intern.h	28 Jul 2005 13:23:13 -0000
@@ -98,6 +98,8 @@ double rb_big2dbl _((VALUE));
 VALUE rb_big_plus _((VALUE, VALUE));
 VALUE rb_big_minus _((VALUE, VALUE));
 VALUE rb_big_mul _((VALUE, VALUE));
+VALUE rb_big_div _((VALUE, VALUE));
+VALUE rb_big_modulo _((VALUE, VALUE));
 VALUE rb_big_divmod _((VALUE, VALUE));
 VALUE rb_big_pow _((VALUE, VALUE));
 VALUE rb_big_and _((VALUE, VALUE));

--
斎藤ただし