AndroidのARTでは実行中にプロファイリングを行ってそれに基づいて再コンパイルするようだ

DalvikVMのJIT vs ARTのAOT

AndroidのDalvikVMではDEXコードをインタプリタで実行しつつ、プロファイリングを行っていて実行頻度の高い部分をJITコンパイルします。コンパイルはメソッドよりも小さな単位で行われ、コンパイルにかかる時間もメモリも小さいのが特徴です。ただし、全体を見渡すような最適化を行うことはできません。コンパイルした結果は保存しません。本当に必要な部分しかコンパイルしないので、コードサイズの増大は最小限です。
それに対して、新しく登場したARTではDEXコードを実行する前にあらかじめコンパイルします。(=AOT: Ahead Of Time compile) コンパイルはインストール時などに行われます。コンパイルには多少時間がかかりメモリも消費しますが、JITに比べて最適化されたコードを生成することができます。コンパイルした結果は保存されるので、毎回のコンパイルは不要です。

AOTの欠点

しかし実行前にコンパイルするということは、実行中に初めてわかる情報、つまりどの部分がより多く実行されるか、ということがわからないままでコンパイルするので、基本的にどの部分も同じような重要度とみなしてコンパイルするしかありません。DEXコードをコンパイルすると元のサイズの10倍以上に大きくなります。そしてサイズよりも速度を優先するように最適化をかけるとさらに数倍のサイズに膨らみます。コードを配置するRAMのサイズは以前よりは大きくはなっていますが、すべてのDEXコードを速度優先でコンパイルするのはさすがにコードサイズの面で厳しいと思います。コードサイズが大きくなりすぎるとキャッシュの効きも悪くなります。

ARTはAOTだけではない!

AOSPのソースをビルドして動かしていたら興味深いログを見つけました。
(横スクロールして見てください。)

I/art     (  636): DexFile_isDexOptNeeded size of new profile file /data/dalvik-cache/profiles/com.android.launcher is significantly different from old profile file /data/dalvik-cache/profile-cache/com.android.launcher (top 90% samples changed in proportion of 18.75%)
I/PackageManager(  636): Running dexopt on: com.android.launcher
I/dex2oat ( 1047): dex2oat: /system/bin/dex2oat --zip-fd=6 --zip-location=/system/priv-app/Launcher2.apk --oat-fd=7 --oat-location=/data/dalvik-cache/system@priv-app@Launcher2.apk@classes.dex --profile-file=/data/dalvik-cache/profiles/com.android.launcher
 ...
I/dex2oat ( 1047): compiling method android.view.View com.android.launcher2.PagedView.getPageAt(int) because its usage is part of top 3.08642% with a percent of 0.205761%
I/dex2oat ( 1047): compiling method void com.android.launcher2.AppsCustomizeTabHost.onFinishInflate() because its usage is part of top 0.617284% with a percent of 0.617284%
I/dex2oat ( 1047): compiling method void com.android.launcher2.CellLayout.(android.content.Context, android.util.AttributeSet, int) because its usage is part of top 2.88066% with a percent of 0.411523%
I/dex2oat ( 1047): compiling method void com.android.launcher2.CellLayout.onDraw(android.graphics.Canvas) because its usage is part of top 3.08642% with a percent of 0.205761%
 ...
I/dex2oat ( 1047): compiling method void com.android.launcher2.Workspace.addInScreen(android.view.View, long, int, int, int, int, int, boolean) because its usage is part of top 3.08642% with a percent of 0.205761%
I/dex2oat ( 1047): dex2oat took 3.496627214s (threads: 1)
I/art     ( 1019): Starting profile with period 10s, duration 30s, interval 10000us.  Profile file /data/dalvik-cache/profiles/com.android.launcher

このログを見ると、実行中に別スレッドでプロファイルを行っていて、そのプロファイル結果で実行頻度の上位のメソッドは再度コンパイルを行っているようです。おそらく最初はサイズ優先の最適化で全体をコンパイルしておいて、実行頻度の高いものを再度コンパイルするときには速度優先の最適化でコンパイルするのでしょう。

"compiling method AAA because its usage is part of top xx% with a percent of yy%" というメッセージをだしているのは
art/compiler/driver/compiler_driver.cc の CompilerDriver::SkipCompilation(const std::string& method_name)

"Starting profile with period 10s, duration 30s, interval 10000us. "のメッセージをだしているのは
art/runtime/profiler.cc の BackgroundMethodSamplingProfiler::Start(int period, int duration, ... )

興味のある方はコードを追いかけてみてください。

プロファイラは現在はデフォルトでは無効になっている

なお、AOSPのリポジトリは日々更新されているので、現在のものではデフォルトではこのプロファイリングが無効になっています。有効にするには以下のようにプロパティをセットします。
"setprop dalvik.vm.profiler 1"

偶然に私が動かしたときには、デフォルトでプロファイラが有効になっていたようです。ただしそのときは過剰にコンパイルしていたようで、エミュレータの動作は明らかに遅くなっていました。
バックグランドでコンパイルするのはマルチプロセッサ向きです。Androidエミュレータで使用しているqemuではマルチプロセッサを効率よくエミュレートすることができません。そのこともあってプロファイラはデフォルトで無効に変更されたのかもしれません。いずれにしてもまだ未完成なので期待して待ちたいと思います。

Andoridの次の正式リリースでは、64bit化とARTが目玉になると思います。どのように仕上げてくるか楽しみです。