Ruby 2.0.0 の require

書いた人:樽家昌也 (Masaya TARUI)

速くなりました

Ruby 2.0 では色々な機能追加や性能向上が行われていますが、そのうちの一つとして、require がまた高速になりました。

現在の Ruby では rubygems によって様々なライブラリが提供されるようになり、これらの gem を用いる事によって自分自身はほんの少しのコードだけ書けば望む機能を実装できるようになってきています。特に Ruby on Rails では rails 本体を始め多くの gem を利用する事が普通になっています。 このため、特に Ruby on Rails で大規模なサイトを構築しようとする場合、その開発過程でこれらの gem を読み込む初期化処理が開発者のストレスとなる場合がありました。

require でのファイルの読み込みが Ruby 1.9 では様々な機能拡張の結果か、Ruby 1.8 と比較して遅くなってしまってから、Ruby 1.9 内でもいくつかの改善を行ってきましたが、Ruby 2.0 では少しの仕様変更によって更に性能が向上しています。 以下のテストベンチでの測定結果を図に示します。 (複数回測定し、一番良い結果を採用しています)

files=2000
begin
    files.times{|i|
        file = "req/file#{i}.rb"
        open(file,"wb"){}
    }
rescue
end
100.times{ $LOAD_PATH << "/" } #gemsの代わり
$LOAD_PATH << "."
start = Time.now
cnt=0
files.times{|j|
    file = "req/file#{j}.rb"
    require file
    cnt +=1
    puts "#{cnt}, #{Time.now - start}" if cnt % 100 == 0
}
require_bench_linux.png require_bench_win.png

これを見ると、1.8.7が未だに最速なのは色々と頑張っている身としては残念ですが、1.9で徐々に挽回してきた歴史と、そろそろ1.8.7と比較しても体感としては問題にならない程度まで高速化されているのが分かると思います。

仕様変更について

先に少しの仕様変更と書きました。恐らくは誰も引っかからない所なのですが、一応書いておきます。

  1. $LOAD_PATH 内の String オブジェクトが freeze されるようになります。
  2. $LOADED_FEATURES 内の String オブジェクトが freeze されるようになります。
  3. String オブジェクトに対して to_path が呼ばれなくなります。

1 はそのままで、require を呼んだタイミングで、$LOAD_PATH 内の String オブジェクトが freeze されるようになりました。そもそも String オブジェクトで新しい Object を生成するのではなく、中身を更新するメソッドがそれほど多くないため、問題になる事は少ないだろうという判断ですが、仮に $LOAD_PATH から Object を取り出したり、$LOAD_PATH に Object を入れてから、それを操作するようなコードを書いている人がいたら悔い改めてください。

2 についても、$LOADED_FEATURES は require したファイルの識別に用いられる配列ですが、freeze されたものが代入されるようになりました。

3 については、皆さんご存知だったかどうか、実は $LOAD_PATH 内のオブジェクトは、to_path メソッドを持っていればそのメソッドの結果を使って文字列に変換し、path としていました。String オブジェクトに対してこの機能は余分である事もあり、to_path メソッドがたとえ定義されていても呼ばれなくなりました。

これらは結局の所、変更が無い場合に実際のファイルパスをキャッシュして高速化する為に行われています。freeze する事によってその中身に変化が無い事を保証しますが、to_path メソッドがあると中身に変化はなくともクラスが修正されると結果を変えないといけないので、実際のファイルパスに変更が無い事を保証する為に呼び出さない事としました。

そのため、上記で何度も String オブジェクトと書いていましたが、$LOAD_PATH 内に String オブジェクト以外を入れてしまうと効果は半減とは言わないまでも少し落ちてしまう為、入れないようにする事をお勧めします *1

なお、$LOAD_PATH や $LOADED_FEATURES に変更が無い事自体の判定の実装では、内部で用いている配列の share フラグを利用する中々面白いギミックが用いられています。このあたりに興味のある方は、是非 https://bugs.ruby-lang.org/issues/7158 も覗いてみてください。

最後に

Ruby 2.0 を使ってより高速な Ruby 生活を送ってください!

著者について

樽家昌也(Masaya TARUI)

No Tool, No Life. 文明って素晴らしい。Let's Civilization!

Windows の IRB 上で生活している。割とスピード狂。ツールが改良されて 1 秒短くなって、万人が使うとすると合算して 1 万秒/回の経済効果! 素晴らしいですね。


*1 入れた事がある人がどの位いるかわかりませんが :-)