RubyでRubyを高速化できるかもしれない話

結論

RubyRubyを高速化できるかもしれない話です。
半分は調べた内容のメモ用で書いてます。

どういうこと?

現在開発中のRubyでは一部の実装をRubyで書くことができるようになっています。(ちなみに、Ruby 2.7ではいくつかのメソッドがRuby(とC)で実装されています)

詳しい話は、RubyKaigi 2019 での ko1さんのこのセッションを見ていただければと思います。

youtu.be

ざっくりいうと、キーワード引数やCコード内で例外処理でよしなにしているケースではRubyで実装したほうが速くなることがあるという感じです。

ちなみに、最近ではいくつかのメソッドを完全にRubyで実装するというケースもあります。(Integer#to_iとか)
今回はそのケースを紹介します。

高速化できるケース

具体的に速くなるケースとしては

  • freezeされたオブジェクト(インスタンス)を返すメソッド
  • 返す値が固定(必ずtrueを返すなど)

この二件が同時に満たされたときですね。

freezeされたオブジェクト(インスタンス)が速いのはまあ何となくわかります。
ですが、返す値が固定というのは「どうしてだろう?」となりますね。

なぜ高速化するのか?

これはおそらくですが、Rubyのメソッドキャッシュによるものではないかと思います。
Rubyにはインラインメソッドキャッシュというキャッシュがあります。

インラインメソッドキャッシュとは、以下のようなコードがあった時「実行されるメソッドの定義は常に同じだろう」と考え、それをキャッシュしておくことで高速化するというものです。

5,times{ puts :hoge }

つまりメソッドが返す値が固定である場合、インラインメソッドキャッシュが効きやすく高速になるということです。

ちなみに、インラインメソッドキャッシュの詳しい内容は以下の記事などを読んでいただければと思います。

magazine.rubyist.net

http://rvm.jp/yarv/imc.presentation.pdf

qiita.com

具体例

以下のようなコードで大体1.3倍くらいとかの高速化ができます。

class TrueClass
  def to_s
    "true".freeze
  end
  alias_method :inspect, :to_s

  def |(bool)
    true
  end
end

ちなみに、ベンチマークはこんな感じです。

benchmark:
  to_s: |
    true.to_s
  inspect: |
    true.inspect
  or: |
    true | false
loop_count: 1000000

これを ruby 2.8.0dev (2020-08-20T04:24:55Z master 6509652c13) [x86_64-linux]にパッチとして当ててベンチマークを実行すると以下のような結果になりました。

sh@MyComputer:~/rubydev/build$ make benchmark/trueclass.yml -e COMPARE_RUBY=~/.rbenv/shims/ruby -e BENCH_RUBY=../install/bin/ruby
# Iteration per second (i/s)

|         |compare-ruby|built-ruby|
|:--------|-----------:|---------:|
|to_s     |     66.001M|   91.927M|
|         |           -|     1.39x|
|inspect  |     70.464M|   97.220M|
|         |           -|     1.38x|
|or       |     61.434M|   86.484M|
|         |           -|     1.41x|

compare-rubyがパッチを当てる前のもので、built-rubyがパッチを当てたものです。
おおよそ1.3~1.4倍くらい速くなりそうです。

おわりに

ほかにもいくつかのメソッドをRubyを高速化できるかもしれないのでベンチマークなどを取りつつパッチを投げたい。

備考

さっそくパッチ投げてみた

bugs.ruby-lang.org

パッチを投げてたけど、テスト回すの忘れてたのでCIで落ちまくってた……