あなとみー おぶ mrubyのJIT (その1)

ふと思い立ってものすごく影の薄いmrubyのJITの内部構造を説明することにしました。mrubyのJITは正式名称がないというとてもかわいそうなmrubyのフォークですが、オリジナルのmrubyの1〜3割(倍じゃないのに注意)速いようです。ここにあります、 https://github.com/miura1729/mruby

さて、mrubyのJITはTracing JITなるものを使っています。第一回目はTracing JITの説明をしたいと思います。コードの解説はしないので、そういうのが読みたい人はTwrtter (@miura1729)かコメントで続きはよとつっついてください。

Tracing JITは今やLuaJIT, Pypyをはじめとするいろんな処理系で使われていますが、あまり解説記事はないようです。ただ、http://dodgson.org/omo/t/?date=20080510 が素晴らしくて他に書きようがないという話かもしれません。これを読んでもらえればいいのですが、mrubyのJITはもっと手を抜いているのでmrubyのJITに即して説明します。

mrubyのJITはこんな感じで動きます。

  • mrubyのVM本体(mrb_run)でどの命令を実行しようかなー?と選ぶ直前のタイミングで制御を取り上げる
    • これから実行しようとするVMの命令が既にコンパイルされていたらそれを呼び出す
    • コンパイルされていないなかったら、その命令に対応する機械語列を生成する
    • 何事も無かったかのようにmrb_runに戻る

こんだけです。ただ、すべての命令列をコンパイルすると、初期化でコンパイルしたけど二度と通らないという悲しいことになりますので、命令が実行した回数を数えて一定数(現状1000回)以上になったらコンパイルするようにしています。

とっても単純でいいのですが、いろいろ疑問もわくことと思います。

  • コンパイルされたコードを呼び出した後戻ってきたらちゃんと続きが実行出来るの?
  • VMで条件分岐命令が出てきたり、違うオブジェクトが変数に入っていた時は?
  • これって遅くない?

では答えていきます。

 コンパイルされたコードを呼び出してその後、VMがちゃんと続きが出来るかということですが、そうなるように作っています(答えになってない気もする)。LuaJITとかだとVMで更新しないといけない情報をコンパイル時に管理してVMに戻るタイミングで更新しているようです。一方、mrubyのJITは常にmrubyのVMの状態をコンパイルされたコードで更新しながら実行しています。簡単ですが、変数をレジスタに割り当てるとか出来なくて遅いです。

 条件分岐命令とかはどうするかですが、ここで説明するのは難しいのでまた次回以降に説明します。キーワードはガードです。気になる人は、http://dodgson.org/omo/t/?date=20080510 を読んでください。

mrubyは手抜きなので輪を掛けてますが、Tracing JITは普通のAOTより遅くなると思いっます。じゃあ、化け物LuaJITは?と思われると思いますが、あれは二段熟カレーならぬ二段コンパイルをしているはずです。特に重いと思った所をじっくりコンパイルします。Pypyはどうやってるだろう?

そういうことで今回はおわり。まだ勢いがあるので第二回はあることでしょう
続く