rubyでRubyVMの機能を利用してプリコンパイルしたソースをrequireする実験をしてみたい

先日のエントリで、Windowsではrubygemsをrequireするだけで0.5秒かかると書いたのですが、rubyオープンソースのプロジェクトであり、僕はプログラマなわけですから、できるかどうかはともかくとして自分で何とかしようとしてみるのが筋というものです。

WindowsrubyのファイルIOが遅いのは既知の問題らしく(こちらの投稿などでも指摘されています)、さらに原因はWindows側にある可能性が高いということなので、修正される見込みは薄そうです。ということは、何か別の解決方法を見つけなくてはいけません。

そこで考えたのが、ruby1.9のRubyVMの機能を利用して、ライブラリファイルをそこから再帰的にrequireしているファイルもまとめてプリコンパイルし、一つのファイルに保存する。requireの代わりにその命令列をloadすれば、読み込むファイルが一つだけで、ファイルサイズも小さいから速いのではないか?ということです。

同様のことを考えた方はこれまでもいたようなのですが、うまくいかなかったのか、その成果は発表されていません。うまくいかなかった理由は、おそらくなのですが、ruby1.9側の準備ができていなかったということもあるのではないかと思います。僕もここ何日か、wanabeさんが作成されたiseqというgemを使わせていただいて、プリコンパイルしたRubyVMの命令列をロードするプログラムを動かしていたのですが、何日か前に動かなかったのが今日動くようになっていたということがありました。(ruby-devでバグが報告され、修正されていました。)

というわけで、どのくらい時間がかかるかわかりませんが、実験をやってみたいと思います。一応以下の手順で進めています。

  • Windowsruby1.9のtrunkをコンパイルできるようにする
  • iseq gemを使えるようにする
    • gemをそのままインストールしてもコンパイラのバージョンが違うので動かない。よって、githubからソースを取得してコンパイルする。
  • iseq gemでrubyの標準ライブラリをコンパイルして、ファイルに保存して、復元してロードしてみる
    • コンパイルは、ソースを文字列として読み込んでISeq.compileすればよい
    • これでRubyVM::InstructionSequenceオブジェクトができる。ISeq.loadが受け取るのはこれを配列に変換したもの
    • よって、配列に変換したものをMarshalでdumpしてファイルに保存し、ロードするときはMarshal.loadしたものをISeq.loadする
    • とりあえずcsv.rbとrubygems.rbでできるのを確認した

とりあえず今日できたのはここまで。以下は今後の目標です。

  • プリコンパイルしたcsv.rbには、require 'forwardable'などの他のファイルへの参照が入っている。このような参照を、同様にプリコンパイルした命令列に置き換える。forwardable.rbも別のソースをrequireしているかもしれないので、これは再帰的にやる必要あり
  • ISeq.loadがやるのはrequireではなくあくまでもloadなので、ファイルを二重に読んでしまう可能性がある。よって、二重に読まないために変数$LOADED_FEATURESに読み込んだファイル名を入れる処理は別途行う必要がある
  • ここまでできたら、ベンチマークを取る。
  • Marshalで命令の配列を読み込んでいるのが遅い場合は、配列ではなくRubyVM::InstructionSequenceを直接Marshalでdump/loadし、ISeq.loadではなくRubyVM::InstructionSequence#evalするような方法を検討する
  • これができたらもう一回ベンチマーク
  • RubyVM::InstructionSequenceを直接Marshalでdump/loadする方法ができたら、拡張ライブラリにしてみたい

…とここまでできたらいいなあ、と今のところは妄想しています。どのくらいでできるかはわかりませんが、少しずつ進めていきたいと思います。