Floatクラスをいじってみる

とりあえず手元のruby 1.8.7 (2009-06-12 patchlevel 174) [i386-mswin32]。


Ruby浮動小数点の実数はFloatクラスとして実装されていて、その中身はCで言うところのdouble型である。
VALUE型は32bitで表されているから64bitのdouble型は格納できず、よく使う型なのにオブジェクトとして生成する。
いまどき2Dゲーでも3Dゲーでも実数表現だから、Floatクラスをメインで使うことになるわけだが、計算のたびにオブジェクトを生成するもんだからけっこう遅い。
一般的にはWebとかテキスト処理とかの用途に使う言語って印象で、Floatオブジェクトなんかを多用するのはほんの一握りかもしれない。
でも、Ruby1.9.1でも整数は速くなったがFloatは変わらず(しょうがないけど)、今後Rubyで3Dゲーを作る環境なんかが出てくるかもしれないことを考えると、Floatクラスの速度にはなんらかの対策が欲しいところだ。
普通にゲームを作るのならdoubleじゃなくてfloatで十分なのだから、Floatクラスをfloat型にしちゃったGameRubyとかどうだろう。
SSE2とかを使うようにコンパイルすれば結構速くなるんじゃなかろうか。
まあ、普及することはないだろうが。


さて、Ruby浮動小数点値がオブジェクトであるということであれば、直値の整数と違って、中身を書き換えることができるはずだ。すなわち自己破壊。
演算用のメソッドは常に計算後の値のオブジェクトを生成して返すようになっていて、例えば「1.0 + 2.0」とすると3.0を生成して返す。
1.0や2.0を破壊してしまうと、両方Floatオブジェクトの「x + y」で変数の中身が壊れるから、当然そうなってはいない。
でも、よく考えてみると壊れてもいいパターンがあるのだ。
「x = x + y」とか。
この場合、xを破壊して中身を書き換えれば、オブジェクトの生成は必要なくなる。
ただ、これを実現するのは大変な話なので、かわりに「x += y」と書いたときに、xがFloatなら自分を書き換える、つまり+=がメソッドであれば、すぐできる。
でも手元のソースのnumeric.cを見てみると+=なんてメソッドは定義されていないから、これはメソッドではないのだろう。
手詰まりだ。


あんまり難しいことはわからないが、例えばFloat#add!(val)てなメソッドを定義することぐらいはできそうだ。
ものは試しに、ってことで、こんな定義文を作って

    rb_define_method( rb_cFloat, "add!", Float_add_bang, 1);

んで、こんな関数を作る。

/*--------------------------------------------------------------------
   Float自己破壊型加算
 ---------------------------------------------------------------------*/
static VALUE Float_add_bang( VALUE obj, VALUE fl )
{
    RFLOAT(obj)->value = RFLOAT(obj)->value + NUM2DBL(fl);

    return obj;
}

こんなRubyのソースを書いて実行すると

require 'floattest'
require 'benchmark'

x = 0.0
puts Benchmark.measure {
  for i in 0...1000000
    x += 1.0
  end
}
p x

x = 0.0
puts Benchmark.measure {
  for i in 0...1000000
    x.add! 1.0
  end
}
p x

結果はこう。

  1.000000   0.000000   1.000000 (  1.031250)
1000000.0
  0.704000   0.000000   0.704000 (  0.703125)
1000000.0

なんかよさそう。