CRubyのFloatクラスの結構なメソッドをbuiltinで書き直してみた

はじめに

Hamada.rbでCRubyのFloatクラスをbuiltinで書き直してみた記事です。どちらかといえば作業ログな内容です。

builtinとは?

過去にいくつか記事にしているので詳細はそちらを見て頂ければと思います

gamelinks007.hatenablog.com
gamelinks007.hatenablog.com
gamelinks007.hatenablog.com

やったこと

とりあえず、お試ししたいだけなので雑にinteger.rbに以下のコードを追加

class Float
  def to_s
    Primitive.attr! 'inline'
    Primitive.cexpr! 'flo_to_s(self)'
  end

  def inspect
    Primitive.attr! 'inline'
    Primitive.cexpr! 'flo_to_s(self)'
  end

  def hash
    Primitive.attr! 'inline'
    Primitive.cexpr! 'flo_hash(self)'
  end

  def to_f
    self
  end

  def abs
    Primitive.attr! 'inline'
    Primitive.cexpr! 'rb_float_abs(self)'
  end

  def magnitude
    Primitive.attr! 'inline'
    Primitive.cexpr! 'rb_float_abs(self)'
  end

  def zero?
    Primitive.attr! 'inline'
    Primitive.cexpr! 'flo_zero_p(self)'
  end

  def to_i
    Primitive.attr! 'inline'
    Primitive.cexpr! 'flo_to_i(self)'
  end

  def to_int
    Primitive.attr! 'inline'
    Primitive.cexpr! 'flo_to_i(self)'
  end

  def nan?
    Primitive.attr! 'inline'
    Primitive.cexpr! 'flo_is_nan_p(self)'
  end

  def infinite?
    Primitive.attr! 'inline'
    Primitive.cexpr! 'rb_flo_is_infinite_p(self)'
  end

  def finite?
    Primitive.attr! 'inline'
    Primitive.cexpr! 'rb_flo_is_finite_p(self)'
  end

  def next_float
    Primitive.attr! 'inline'
    Primitive.cexpr! 'flo_next_float(self)'
  end

  def prev_float
    Primitive.attr! 'inline'
    Primitive.cexpr! 'flo_prev_float(self)'
  end

  def positive?
    Primitive.attr! 'inline'
    Primitive.cexpr! 'flo_positive_p(self)'
  end

  def negative?
    Primitive.attr! 'inline'
    Primitive.cexpr! 'flo_negative_p(self)'
  end
end

次にnumeric.cで以下の部分のコードを削除

    rb_define_method(rb_cFloat, "to_s", flo_to_s, 0);
    rb_define_alias(rb_cFloat, "inspect", "to_s");

    rb_define_method(rb_cFloat, "hash", flo_hash, 0);
    rb_define_method(rb_cFloat, "to_f", flo_to_f, 0);
    rb_define_method(rb_cFloat, "abs", rb_float_abs, 0);
    rb_define_method(rb_cFloat, "magnitude", rb_float_abs, 0);
    rb_define_method(rb_cFloat, "zero?", flo_zero_p, 0);

    rb_define_method(rb_cFloat, "to_i", flo_to_i, 0);
    rb_define_method(rb_cFloat, "to_int", flo_to_i, 0);

    rb_define_method(rb_cFloat, "nan?",      flo_is_nan_p, 0);
    rb_define_method(rb_cFloat, "infinite?", rb_flo_is_infinite_p, 0);
    rb_define_method(rb_cFloat, "finite?",   rb_flo_is_finite_p, 0);
    rb_define_method(rb_cFloat, "next_float", flo_next_float, 0);
    rb_define_method(rb_cFloat, "prev_float", flo_prev_float, 0);
    rb_define_method(rb_cFloat, "positive?", flo_positive_p, 0);
    rb_define_method(rb_cFloat, "negative?", flo_negative_p, 0);

あとはbenchmarkディレクトリ以下にbenchmark.ymlを以下のように作成します

prelude: |
  n = 4.2
benchmark: 
  to_s: |
    n.to_s
  inspect: |
    n.inspect
  hash: |
    n.hash
  to_f: |
    n.to_f
  abs: |
    n.abs
  magnitude: |
    n.magnitude
  zero?: |
    n.zero?
  to_i: |
    n.to_i
  to_int: |
    n.to_int
  nan?: |
    n.nan?
  infinite?: |
    n.infinite?
  finite?: |
    n.finite?
  next_float: |
    n.next_float
  prev_float: |
    n.prev_float
  positive?: |
    n.positive?
  negative?: |
    n.negative?
loop_count: 20000000

最後にmake && make installでビルドして make benchmark/benchmark.yml -e COMPARE_RUBY=~/.rbenv/shims/ruby -e BENCH_RUBY=../install/bin/rubyベンチマークを取ってみた

f:id:gamelinks007:20200908231210p:plain

built-rubyruby 3.0.0dev (2020-09-07T04:29:42Z master 17a27060a7) [x86_64-linux]にさっきのbuiltinをパッチとして当てたもの

だいたい同じくらいかちょっと速いくらいになる模様

知りたかったこと

すこし前にInteger#sizeをbuiltinで書くとちょっと速くなるというチケットを書いた

bugs.ruby-lang.org


で、どうも自分自身のみをレシーバとして受け取るメソッドはbuiltinでちょっと速くなりそうな傾向があるような気がしたので試してみた感じです。

Cでの実装がどうなっているかにもよりそうだけどちょっと速くできるかもしれないことが分かったので良かった。