Android ランタイム(ART)チームは、コンパイル済みコードやピーク時のメモリ回帰を損なうことなく、コンパイル時間を 18% 短縮しました。この改善は、メモリ使用量やコンパイル済みコードの品質を犠牲にすることなくコンパイル時間を短縮することを目的とした、2025 年の取り組みの一環です。
コンパイル時の速度を最適化することは、ART にとって非常に重要です。たとえば、ジャストインタイム(JIT)コンパイルは、アプリケーションの効率とデバイス全体のパフォーマンスに直接影響します。コンパイルが速くなると、最適化が開始されるまでの時間が短縮され、ユーザー エクスペリエンスがスムーズになり、応答性が向上します。さらに、JIT と事前(AOT)の両方で、コンパイル時間の短縮はコンパイル プロセス中のリソース消費の削減につながり、特にローエンド デバイスでバッテリー駆動時間とデバイスの温度にメリットをもたらします。
これらのコンパイル時間の短縮の一部は 2025 年 6 月の Android リリースでリリースされ、残りは年末の Android リリースでリリースされる予定です。さらに、バージョン 12 以降のすべての Android ユーザーは、メインライン アップデートを通じてこれらの改善を受け取ることができます。
最適化コンパイラの最適化
コンパイラの最適化は常にトレードオフのゲームです。スピードを無料で手に入れることはできません。何かを犠牲にする必要があります。コンパイラを高速化する。ただし、メモリ回帰を導入せず、重要な点として、生成されるコードの品質を低下させない。これは、Google が自らに課した非常に明確で困難な目標です。コンパイラが高速になってもアプリの実行速度が遅くなったら、失敗です。
Google が費やしてもよいと考えていた唯一のリソースは、これらの厳しい基準を満たす巧妙なソリューションを深く掘り下げ、調査し、見つけるための開発時間でした。改善すべき点を見つけ、さまざまな問題に対する適切なソリューションを見つけるための取り組みについて詳しく見ていきましょう。
価値のある最適化の可能性を見つける
指標の最適化を開始する前に、その指標を測定できる必要があります。そうしないと、改善されたかどうかを判断できません。幸いなことに、変更の前後に測定に使用するデバイスを同じものにする、デバイスでサーマル スロットリングが発生しないようにするなどの対策を講じれば、コンパイル時間の速度はかなり一定になります。さらに、コンパイラ統計などの決定論的な測定値も取得しており、内部で何が起こっているかを把握するのに役立っています。
これらの改善のために犠牲にしたのは開発時間だったため、できるだけ早くイテレーションを行えるようにしたいと考えていました。そのため、Google は代表的なアプリ(ファーストパーティ製アプリ、サードパーティ製アプリ、Android オペレーティング システム自体を組み合わせたもの)をいくつか選び、ソリューションのプロトタイプを作成しました。その後、手動テストと自動テストの両方で広範囲にわたって、最終的な実装が価値のあるものであることを確認しました。
厳選した APK のセットを使用して、ローカルで手動コンパイルをトリガーし、コンパイルのプロファイルを取得して、pprof を使用して時間の費やしどころを可視化します。
pprof のプロファイルのフレームグラフの例
pprof ツールは非常に強力で、データをスライス、フィルタ、並べ替えて、たとえば、どのコンパイラ フェーズまたはメソッドに最も時間がかかっているかを確認できます。pprof 自体の詳細については説明しません。バーが大きいほど、コンパイルに時間がかかったことを意味します。
これらのビューの 1 つは、どのメソッドが最も時間を要しているかを確認できる「ボトムアップ」ビューです。下の画像では、コンパイル時間の 1% 以上を占める Kill というメソッドを確認できます。その他の上位の方法についても、このブログ投稿の後半で説明します。
プロファイルのボトムアップ ビュー
最適化コンパイラには、グローバル値番号付け(GVN)と呼ばれるフェーズがあります。全体として何をするかを気にする必要はありませんが、関連する部分は、フィルタに従って一部のノードを削除する `Kill` というメソッドがあることを知っておくことです。すべてのノードを反復処理して 1 つずつ確認する必要があるため、時間がかかります。その時点でアクティブなノードに関係なく、チェックが false になることが事前にわかっているケースがあることがわかりました。このような場合、反復処理を完全にスキップすることで、1.023% から約 0.3% に減らし、GVN のランタイムを約 15% 改善できます。
価値のある最適化の実装
時間の測定方法と、時間の費やされている場所の検出方法について説明しましたが、これはほんの始まりにすぎません。次のステップは、コンパイルにかかる時間を最適化する方法です。
通常、上記の `Kill` のようなケースでは、ノードの反復処理の方法を確認し、並列処理やアルゴリズム自体の改善などによって処理を高速化します。実際、最初はそうしようとしましたが、何もすることが見つからなかったときに「ちょっと待てよ…」と思い、ソリューションは(場合によっては)まったく反復処理しないことだと気づきました。このような最適化を行う場合、木を見て森を見ないということが起こりがちです。
その他のケースでは、次のようなさまざまな手法を使用しました。
- ヒューリスティクスを使用して、最適化で有益な結果が得られないためスキップできるかどうかを判断する
- 計算されたデータをキャッシュに保存するために追加のデータ構造を使用する
- 現在のデータ構造を変更して速度を向上させる
- 場合によっては、結果を遅延計算してサイクルを回避する
- 適切な抽象化を使用する - 不要な機能はコードを遅くする可能性がある
- 頻繁に使用されるポインタを多くの読み込みで追跡しないようにする
最適化を行う価値があるかどうかを判断するにはどうすればよいですか?
それが、この機能の優れた点です。コンパイル時間が長くなっている領域を特定し、開発時間を費やして改善を試みても、解決策が見つからないことがあります。何もする必要がない場合や、実装に時間がかかりすぎる場合、別の指標が大幅に回帰する場合、コードベースの複雑さが増す場合などがあります。このブログ投稿で紹介している最適化の成功例の裏には、実現しなかった無数の最適化が存在します。
同様の状況にある場合は、できるだけ少ない作業で指標をどの程度改善できるかを推定してみてください。つまり、次のようになります。
- すでに収集した指標または直感に基づいて見積もる
- 簡単なプロトタイプによる見積もり
- ソリューションを実装します。
ソリューションの欠点を見積もることも忘れないでください。たとえば、追加のデータ構造に依存する場合、どの程度のメモリを使用するつもりですか?
詳細を確認する
それでは、実装した変更点をいくつかご紹介します。
FindReferenceInfoOf というメソッドを最適化する変更を実装しました。このメソッドは、ベクトルを線形検索してエントリを見つけていました。そのデータ構造を更新して、命令の ID でインデックス登録できるようにしました。これにより、FindReferenceInfoOf は O(n) ではなく O(1) になります。また、サイズ変更を避けるために、ベクトルを事前に割り当てました。ベクトルに挿入したエントリ数をカウントする追加のフィールドを追加する必要があったため、メモリをわずかに増やしましたが、ピーク時のメモリは増えなかったため、小さな犠牲です。これにより、LoadStoreAnalysis フェーズが 34 ~ 66% 高速化され、コンパイル時間が約 0.5 ~ 1.8% 改善されました。
HashSet のカスタム実装があり、複数の場所で使用しています。このデータ構造の作成にかなりの時間がかかっていましたが、その理由がわかりました。このデータ構造は、非常に大きな HashSet を使用するいくつかの場所でのみ使用され、そのために最適化されました。しかし、最近では、エントリが少なく、有効期間が短い状態で逆方向に使われることが多くなっています。つまり、この巨大な HashSet を作成しても、数個のエントリにしか使用せず、その後破棄するため、サイクルを無駄にしていたことになります。この変更により、コンパイル時間が約 1.3 ~ 2% 改善されました。また、以前ほど大きなデータ構造を使用しなくなったため、メモリ使用量が約 0.5 ~ 1% 減少しました。
ラムダに参照でデータ構造を渡すことで、コピーを回避し、コンパイル時間を約 0.5 ~ 1% 改善しました。これは、元のレビューで見落とされ、何年もコードベースに残っていたものです。pprof のプロファイルを確認したことで、これらのメソッドが多くのデータ構造を作成して破棄していることに気づき、調査と最適化につながりました。
コンパイルされた出力を書き込むフェーズを、計算された値をキャッシュ保存することで高速化しました。これにより、コンパイル時間の合計が約 1.3 ~ 2.8% 改善されました。残念ながら、追加の簿記は負担が大きすぎ、自動テストでメモリの回帰が検出されました。その後、同じコードを再度確認し、メモリの回帰に対処するだけでなく、コンパイル時間をさらに約 0.5 ~ 1.8% 改善する新しいバージョンを実装しました。この 2 回目の変更では、2 つのデータ構造の 1 つを削除するために、このフェーズの動作方法をリファクタリングして再考する必要がありました。
最適化コンパイラには、パフォーマンスを向上させるために関数呼び出しをインライン化するフェーズがあります。インライン化するメソッドを選択するために、計算を行う前にヒューリスティックと、作業後かつインライン化を確定する直前の最終チェックの両方を使用します。これらのいずれかでインライン化する価値がない(新しい命令が多すぎるなど)と判断された場合、メソッド呼び出しはインライン化されません。
時間のかかる計算を行う前にインライン化が成功するかどうかを推定するため、「最終チェック」カテゴリの 2 つのチェックを「ヒューリスティック」カテゴリに移動しました。これは推定値であるため完全ではありませんが、新しいヒューリスティックは、パフォーマンスに影響を与えることなく、以前インライン化されていたものの 99.9% をカバーしていることが確認されています。これらの新しいヒューリスティックの 1 つは必要な DEX レジスタに関するもので(約 0.2 ~ 1.3% の改善)、もう 1 つは命令数に関するものでした(約 2% の改善)。
BitVector のカスタム実装があり、複数の場所で使用しています。特定の固定サイズのビットベクトルに対して、サイズ変更可能な BitVector クラスをよりシンプルな BitVectorView に置き換えました。これにより、間接参照とランタイム範囲チェックの一部が排除され、ビットベクトル オブジェクトの構築が高速化されます。
さらに、BitVectorView クラスは、基盤となるストレージ タイプでテンプレート化されました(以前の BitVector のように常に uint32_t を使用するのではなく)。これにより、Union() などの一部のオペレーションで、64 ビット プラットフォームで 2 倍のビットを同時に処理できます。Android OS のコンパイル時に、影響を受ける関数のサンプルが合計で 1% 以上削減されました。これは、いくつかの変更 [1、2、3、4、5、6] にわたって行われました。
すべての最適化について詳しく説明すると、一日中かかってしまいます。その他の最適化にご興味をお持ちの場合は、Google が実施したその他の変更をご覧ください。
- 簿記を追加して、コンパイル時間を約 0.6 ~ 1.6% 改善します。
- 可能であれば、データを遅延計算して、サイクルを回避します。
- 使用されない場合に事前計算の作業をスキップするようにコードをリファクタリングします。
- アロケータを他の場所から簡単に取得できる場合は、依存するロードチェーンの一部を回避します。
- 不要な作業を避けるためにチェックを追加する別のケース。
- レジスタ アロケータでレジスタ タイプ(コア/FP)の頻繁な分岐を避けます。
- コンパイル時に一部の配列が初期化されていることを確認します。clang に頼らないでください。
- ループをクリーンアップします。clang がより最適化できる範囲ループを使用します。これは、ループの副作用によりコンテナの内部ポインタを再読み込みする必要がないためです。各入力に対してインライン化された `InputAt(.)` を介して、ループ内で仮想関数 `HInstruction::GetInputRecords()` を呼び出すことを避けます。
- コンパイラ最適化を利用して、ビジター パターンで Accept() 関数を回避します。
まとめ
ART のコンパイル時間の短縮に注力した結果、大幅な改善が実現し、Android の流動性と効率性が向上しただけでなく、バッテリー駆動時間とデバイスの温度にも貢献しています。最適化を念入りに特定して実装することで、メモリ使用量やコード品質を損なうことなく、コンパイル時間を大幅に短縮できることを実証しました。
この過程では、pprof などのツールを使用したプロファイリング、反復処理の実施、成果の少ない方法の放棄などを行いました。ART チームの共同作業により、コンパイル時間が大幅に短縮されただけでなく、将来の進歩に向けた基盤も築かれました。
これらの改善はすべて、2025 年末の Android アップデートで利用可能になるほか、Android 12 以降では Mainline アップデートを通じて利用可能になります。この最適化プロセスの詳細な説明が、コンパイラ エンジニアリングの複雑さとメリットについて有益な情報を提供できれば幸いです。
続きを読む
-
プロダクト ニュース
デベロッパーの AI ワークフローとニーズはそれぞれ異なるため、AI が開発にどのように役立つかを選択できることが重要です。1 月には、Android Studio の AI 機能にローカルまたはリモートの AI モデルを選択できる機能を導入しました。
Matthew Warner • 所要時間: 2 分
-
プロダクト ニュース
Android Studio Panda 3 が安定版となり、本番環境で使用できる準備が整いました。このリリースでは、AI を活用したワークフローをさらに細かく制御してカスタマイズできるようになり、高品質の Android アプリをこれまで以上に簡単に構築できるようになります。
Matt Dyor • 所要時間: 3 分
-
プロダクト ニュース
Google は、最も高性能な AI モデルを直接 Android デバイスに搭載することに取り組んでいます。本日、Google の最新の最先端オープンモデルである Gemma 4 のリリースを発表いたします。
Caren Chang, David Chou • 所要時間: 3 分
メールを受け取る
Android 開発に関する最新の分析情報を毎週メールでお届けします。