2010年05月26日
AndroidのFroyoのJITについて
先日サンフランシスコで開催されたGoogle I/OのキーノートでAndroid2.2(コードネーム:Froyo)でJITコンパイラが搭載されることが発表されました。そしてJITに関するセッションもありました。私はサンフランシスコに行ったわけではありませんが、現地からUSTREAM中継してくれた方がいたおかげでセッションのスライドを見ることができました。また、そのセッションのGoogle Waveにも有用な情報が載っています。それらから情報を拾ってまとめてみました。
いつコンパイルするか?何をコンパイルするか?
通常のJITコンパイラはメソッド単位でコンパイルを行いますが、それ以外の方法としては、メソッドの一部のみをコンパイルする方法があります。後者を"Trace granularity JIT"と呼んでいます。
後者はインタープリタの中で実行頻度の高い部分を見つけておいて、その部分だけをコンパイルします。
実際にはJITコンパイルは別スレッドで行います。コンパイルすると決めた部分はコンパイルスレッドのキューに積んでおきます。
最適化されたコードを生成するという点ではメソッド単位のコンパイルに軍配が上がりますが、トレースコンパイルの方がスピードとサイズの点で優れていることがわかりました。それでAndoridではトレースコンパイルを採用したそうです。でも将来的には両方のよいところを組み合わせて使えるようにするそうです。
性能評価
ベンチマークテストでは2-6倍の性能向上が見られました。
実際のアプリケーションでOProfileというツールでプロファイル計測をしてみました。
Robo Defenceというアクション系のゲームでは75%の時間はskia描画ライブラリでDalvikVMではわずか4%でした。JITで向上する部分はDalvikVMでの部分だけなので、このゲームではJITの効果はほとんど現れてきません。
Checkerというパズルゲームでは大半の時間をDalvikVMの実行が占めていました。このケースはJITの効果が高いです。これにJITを適用した結果94%の時間はJITのコードキャッシュの中の実行になりました。
JITのコードキャッシュはそんなに大きなものではありません。もしそれが一杯になった時には全て消去してまた最初からやりなおします。(どれを消してどれを残すという判断をするしくみは無い。コンパイルが充分速いならば、このほうが合理的。)
統計情報
起動から20分間で 9898回コンパイル 合計796264バイト (1回あたり80バイト)
生成したネイティブコードサイズは元のDexコードから約7.7倍。
コンパイルの合計時間は6秒 (1回の平均は0.6msec)
ここで目を引くのがコンパイル時間の短さです。トレースコンパイルでコンパイルの単位を小さくしたことが奏功しています。一回の平均時間は0.6msecはかなり短いと思います。ガベージコレクションで一回に数十msecかかることと比較すると誤差の範囲です。
どうやってJITコンパイラをデバッグするか
セッションスライドの最後の方はどうやってJITコンパイラをデバッグするかという話でした。たくさんのassertionを埋め込んで、インタプリタで実行した場合とJITコンパイルした場合でVMの状態変化をstep by stepで確認したりするそうです。苦労がしのばれます。
このあたりはこのセッションの動画が公開されたらまた見てみたいところです。
最後に
Nexus OneではすでにFroyoへのアップデートが始まっているそうですが、アップデートしても体感的な速度は「あまり変わらない」そうです。通常のアプリの実行ではDalvikVMはボトルネックではないのでJITによってはっきりわかるほど速くなることはないわけですが、逆にJITの欠点であるコンパイルすることによる動作のもたつきを体感的にわからない程度に軽減しているのは優秀です。
ソースが公開されたらまたじっくり調べてみようと思います。
2010.5.27追記
DalvikVMの開発者のブログによると、今回のJITで2つの点を特に誇りに思っているそうです。
- コンパイル時間の短さ(コンパイルによるディレイがほとんど感じられない)
- メモリ消費量の少なさ。(100KB程度の追加のワークメモリで充分動かせる)
確かにこの点で素晴らしい。
2010.5.31追記
Eclairで入っていた実験版のJITの話はこちらのスライドの後半をどうぞ。
2010.6.10追記
この話題でCELFテクニカルジャンボリーとGoogle I/O報告会で話をしました。