事例紹介

Grab、R8 とプロファイルに基づく最適化により ANR を 32% 削減、起動時間を 35% 改善

所要時間: 5 分

Grab は、東南アジアを代表するスーパーアプリです。1 つのアプリで、8 か国の何百万人ものユーザーにデリバリー、モビリティ、金融サービスを提供しています。Grab チームは、10 年前のコードベース(1,000 万行を超えるコードと、Bazel と Gradle のハイブリッド設定を使用して構築された数百のサードパーティ ライブラリを含む)を抱えており、3 つの課題に直面していました。それは、アプリケーション応答なし(ANR)の発生率が高いこと、スーパーアプリに新しいサービスが追加されるにつれてアプリのサイズが増加し続けること、起動時間が年々徐々に増加していることでした。R8 の最適化を有効にし、保持ルールを改良して、ベースライン プロファイルと起動プロファイルを実装したことで、Grab はユーザーが認識した ANR 発生率を 32% 削減し、アプリのダウンロード サイズを約 30% 削減し、起動時間を 35% 改善しました。

リリース後の ANR 急増を排除してユーザー エクスペリエンスを向上

ビルドプロセスを最適化する前、Grab は新しいアップデートの直後にコールド スタートの遅延とアプリケーション応答なし(ANR)の急増に直面していました。これは主に、アプリの初期化中に多くの DEX ファイルを読み込むオーバーヘッドが原因でした。

Google Play のクラウド プロファイルは、ユーザーベース全体で標準フローの最適化を提供しますが、Grab のクリティカル ユーザー ジャーニーに必要なすべての初期化パス(アプリ起動時の Dagger 依存関係グラフの初期化など)を最適化するわけではありません。初期配布に含まれていないコードパスについては、デバイスは JIT コンパイルと、その後の数日間にわたるローカル バックグラウンド最適化に依存していました。カスタムの ベースライン プロファイルを実装することで、これらのクリティカル ユーザー ジャーニーを Google Play のクラウド プロファイルに直接提供し、最適化の対象範囲を拡大して、これらの主要なパスがインストール時にすぐにマシンコードにコンパイルされるようにすることで、この問題を解決しました。

ANDDM_Grab_Inline_Quote_ALT.png

「毎週のアプリリリースで、ANR 発生率の急増が常に発生していました」と、Grab の Passenger App Core チームのリード ソフトウェア エンジニアである Nguyen Van Minh 氏は説明します。

ANR を排除して起動時間を短縮するため、Grab は R8 の最適化、ベースライン プロファイル、起動プロファイルを実装しました。プロファイルに基づく最適化と R8 オプティマイザーを併用することで、R8 で 12%、ベースライン プロファイルでさらに 15% のコールド スタート改善がすぐに実現しました。起動時間は 27% 短縮され、リリース後の ANR の急増を効果的に排除できました。

Grab は、このアプリの最適化を 3 つの戦略的フェーズで実施しました。互換モードで R8 の最適化を有効にし、R8 フルモードに移行した後、構成アナライザーで Keep ルールを調整し、CI/CD ガバナンスを追加しました。

フェーズ 1: R8 の最適化を有効にする(2025 年にリリース)

この最初の段階で、Grab は従来のデフォルトの Proguard ファイルから、R8 の最適化を可能にする proguard-android-optimize.txt に移行しました。コードベースは 10 年以上前のもので、1, 000 万行を超えるコードが含まれているため、オプティマイザーを有効にするには、レガシー構成とリフレクションの使用に対処する必要がありました。

このフェーズの効果を最大化するため、Grab は R8 互換モードを有効にしたアプリ アップデートを、ベースライン プロファイルとスタートアップ プロファイルとともにリリースしました。この取り組みにより、ユーザーが認識した ANR 発生率が 25% 減少し、アプリのダウンロード サイズが 16% 縮小し、コールド スタート時間が全体で 27% 改善しました。

フェーズ 2: R8 フルモードと移行(2026 年リリース)

最初のロールアウトから 9 か月後に開始されたこのフェーズでは、アプリを R8 フルモードに移行し、さらなる最適化を実現しました。Retrofit、Firebase、Jetpack Compose などの標準ライブラリは、組み込みルールを使用して R8 とシームレスに連携しましたが、残りの大規模なコードベースの移行は、リフレクションを多用する特定のコードパスを解決することで実現しました。

R8 フルモードに移行するため、チームは最新の Gson リリースのコンシューマー保持ルールを採用し、シームレスな JSON 解析を実現しました。また、Java の動的プロキシによる最適化の課題にも対処しました。動的プロキシは、リフレクションを使用して実行時にインターフェースの実装を生成します。R8 はコンパイル中にこのランタイムの使用を検出できないため、必要なクラスを削除または名前変更し、アプリケーションがクラッシュします。アプリのサイズを肥大化させるような広範な例外を適用するのではなく、リフレクションに依存する特定のクラスを分離し、ターゲットを絞った keep ルールを実装することで、アプリの安定性と最適化の効率を両立させました。

最終的に、R8 フルモードは、アプリのダウンロード サイズをさらに 14% 削減、起動時間をさらに 10% 改善、ユーザーが認識した ANR をさらに 10% 削減するなど、大きな複合的な改善をもたらしました。R8 フルモードへの移行後、Grab のコードベースは、以下の R8 最適化スコアを達成しました。

  • 最適化スコア: 72%
  • 縮小化スコア: 73%
  • 難読化スコア: 79.6%

これらのスコアは、R8 構成に関するフィードバックと、R8 の最適化を最大化するために保持ルールを改良する必要性を示しています。

フェーズ 3: 保持ルールの調整 

最適化プロセスの最終フェーズでは、Grab は長期的なコードベースの健全性、継続的なパフォーマンス モニタリング、厳格なコンパイル ガバナンスの確立に注力しています。ルール ハイジーンを体系的に測定し、進行中のリリース サイクル全体で構成の回帰を防ぐために、チームは R8 構成アナライザを評価ワークフローに統合しています。このツールを使用すると、Grab の保持ルールによって R8 がアプリケーションをどの程度効果的に最適化、圧縮、難読化できるかを詳細に把握できます。

R8 フルモードの基盤を構築し、レガシーの keep ルールを体系的に削除することで、Grab はアナライザーで評価される 3 つの主要な側面すべてで大幅な改善を実現しています。

  • 最適化スコア: 72% から 78.2% に増加
  • 縮小化スコア: 73% から 79.1% に増加
  • 難読化スコア: 79.6% から 81.7% に増加

この専用の keep ルール改良フェーズにより、診断スコアが向上しただけでなく、アプリのダウンロード サイズがさらに約 3.6% 削減されました。さらに重要なのは、これらの指標を追跡することで、エンジニアリング チームが、マージ前の CI ガードレールを自動的に実装するために必要な客観的なデータを得られることです。これにより、新しく導入されたコードが厳格な keep ルール標準に準拠し、苦労して得たパフォーマンスの向上が長期にわたって維持されるようになります。

効果

R8 オプティマイザーのロールアウト後の主なパフォーマンス改善は次のとおりです。

最適化フェーズANRサイズコールド スタート
フェーズ 1: R8 オプティマイザー(2025 年リリース)約 25% 削減16% 削減約 27% 改善
フェーズ 2: R8 フルモード(2026 年リリース)10% 削減14% 削減10% 改善
保持ルールの最適化-3.6% 削減-
全体(累計)約 32.5% 削減約 30.4% 削減約 35% 改善

技術的な課題 

10 年前の Grab のコードベース全体で R8 オプティマイザーを有効にすると、大規模な技術的課題が生じました。

  • テスト: 10 年にわたって蓄積された反射により、完全な最適化の下でエッジケースが作成されました。数百万行のコードを網羅的にテストすることは不可能だったため、チームは段階的かつ優先度の高いアプローチを採用しました。具体的には、絞り込まれたキープルールでカバーされるコードにテストを集中させ、それらのルールを繰り返し改良して、リフレクションに必要な特定のクラス、メソッド、フィールドのみをターゲットにすることで、R8 の最適化を最大化しました。
  • ビルド レイテンシとハイブリッド アーキテクチャ: Grab のハイブリッド ビルドシステム(デバッグ用の Bazel、R8 リリースビルド用の Gradle)では、R8 の試験運用版の変更をリモート CI で行うのに最大 2 時間を要していました。フィードバックのギャップを埋めるため、Grab は日々の QA ビルドを本番環境の R8 の動作に合わせ、mapping.txt や configuration.txt などの主要な CI ビルド アーティファクトの収集を自動化して、調査を効率化しました。
  • デバッグ サイクルの遅さ: 最適化された数百のクラスにわたって根本原因を追跡することは、少数精鋭のチームにとって大きな負担となっていました。Grab は、Gradle を介して Firebase Crashlytics へのマッピング ファイルのアップロードを自動化し、Google Play Console の難読化解除マッピングをバンドルすることで、この問題を解決しました。これにより、ランタイムの問題に対して、迅速かつ正確なシンボル解決が可能になりました。

R8 がコードに及ぼす長期的な影響

R8 オプティマイザーを有効にすると、パフォーマンスがすぐに向上するだけでなく、Grab の大規模なコードベースに永続的な構造上のメリットがもたらされました。Grab のチームは、オープンソース ツールの app-sizer を使用して R8 の影響を測定し、R8 によってアプリのすべてのコード行のダウンロード サイズが永続的に削減されることを確認しました。

ANDDM_Grab_Inline_Metric.png

コード量が急速に増加する AI を活用した開発の時代において、この最適化はさらに重要になっています。R8 を使用しない場合、コードの拡張に比例してダウンロード サイズが増加します。R8 では、コードの拡張率を同じに保ちながら、メモリ使用量を大幅に削減できます。

注: 方法: app-sizer の Kotlin/Java がアプリのダウンロード サイズ(代表的なデバイス)に占める割合を、本番環境の Kotlin LOC(cloc、メインのソースセットのみ、テスト、モック、サンプルのモジュールを除く)で割った値。

Keep のルール管理とガードレール

ルールの劣化を防ぎ、R8 の最適化を長期にわたって維持するために、Grab は自動化されたマージ前のガードレールを CI パイプラインに直接統合することを計画しています。このポリシーでは、新しい保持ルールは厳しく制限され、追加のルールには明示的な理由と審査が必要になります。Grab のチームは、CI で R8 の最適化スコアを追跡しています。これに基づいて、チームはこれらの指標を継続的なフィードバック ループとして使用することを計画しています。これにより、マージ前に回帰が検出され、すべてのリリース サイクルで最適化のメリットが維持されます。

R8-rules-score (1).png

図 1: Grab ビルドの R8 圧縮、最適化、難読化のスコアを時系列で追跡する CI ダッシュボード。リリース全体で回帰を事前に特定して防止します。

まとめ 

Grab の Pax App Core チームは、大規模で成熟したコードベースの課題に直面し、ベースライン プロファイルを実装して R8 構成を継続的に改善することで、起動時間の増加とリリース後のパフォーマンスの急増に積極的に取り組みました。ユーザーが認識した ANR を約 32.5% 削減し、アプリのダウンロード サイズを約 30.3% 削減し、コールド スタート時間を約 35% 改善すると同時に、コードベースのスケーリング効率を根本的に改善しました。

これらの最適化により、東南アジアの何百万人ものユーザーに、より高速で安定したスーパーアプリ エクスペリエンスを提供すると同時に、Grab のエンジニアは、ダウンロード サイズの急激な増加を招くことなく、機能をより迅速に構築するためのツールを手に入れることができました。

R8 の詳細と使用方法については、 d.android.com/r8 をご覧ください。

作成者:
続きを読む