この記事は Chrome ソフトウェア エンジニア、Clark Duvall による Chromium Blog の記事 “Partitioning Chrome’s Code for Faster Launch Times on Android” を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
通常、モバイル デバイスは、ノートパソコンやデスクトップよりもリソースが限られています。モバイル ユーザーが Chrome を高速に使えるようにするには、Chrome のリソース使用の最適化が欠かせません。Android 版の Chrome に機能を追加するにつれて、アプリにパッケージ化される Java コードの量は増え続けています。今回の速さと好奇心の投稿では、Isolated Splits によって Android 版 Chrome のスピードとメモリ使用量をどのように改善したのかについて説明します。この改善により、Android 版 Chrome のメモリ使用量が 5-7% 減少し、起動とページ読み込みの速度もさらに向上しました。
問題
Android アプリ(Android 版 Chrome も含む)では、コンパイルされた Java コードが .dex ファイルに格納されます。Android 版 Chrome にはマルチプロセス アーキテクチャが採用されているため、そのユーザー エクスペリエンスが .dex サイズの増加に特に影響されやすくなります。通常、Android の Chrome では、ブラウザ プロセス、GPU プロセス、1 つ以上のレンダラ プロセスという 3 つ以上のプロセスが常に実行されています。Chrome の Java コードの大半はブラウザ プロセスでのみ使われます。しかし、そのコードを読み込むためのパフォーマンスとメモリのコストは、すべてのプロセスが支払うことになります。
バンドルと機能モジュール
プロセスを実行するために必要な最小チャンクの Java を読み込むことができれば理想的です。Android App Bundle を使ってコードを機能モジュールに分割することで、それに近づくことができます。機能モジュールを使うと、コードやリソース、アセットを個別の APK に分割し、オンデマンドでもアプリのインストール時でも、ベース APK とともにインストールできます。
ということは、まさに必要としているものが手に入りそうです。つまり、ブラウザ プロセスのコード用機能モジュールを作り、必要なときにそれを読み込むことができるかもしれません。しかし、Android はそのようにして機能モジュールを読み込むわけではありません。デフォルトで、すべてのインストールされている機能モジュールは起動時に読み込まれます。ベース モジュールと 3 つの機能モジュール “a”、”b”、”c” があるアプリなら、Android の Context と、次のような ClassLoader が得られます。
状況によっては、インストールするモジュールを最低限にとどめ、起動時にこれらのモジュールすべてを即座に読み込むという方法が役立つこともあります。たとえば、一部のユーザーしか必要としない大きな機能がある場合、必要のないユーザーはそれをまったくインストールしないようにします。しかし、一般的に使われる機能の場合、実行時に機能をダウンロードしなければならないと、ユーザーは不便を感じる可能性があります。たとえば、動作が遅くなったり、モバイルデータが利用できないときに問題になったりします。理想的な方法は、標準モジュールをすべて事前にインストールしておいて、実際に必要になったときのみ読み込むことです。
解決策は Isolated Splits
数日間 Android ソースコードを探し続けた結果、android:isolatedSplits という属性が見つかりました。これを “true” に設定すると、インストールされた分割 APK が起動時に読み込まれなくなり、明示的な読み込みが必要になります。これこそ、プロセスのリソース使用量を減らすために必要としていたものです。これにより、先ほどの ClassLoader は次のようになります。
Chrome では、レンダラーや GPU プロセスに必要な少量のコードを引き続きベース モジュールに配置し、ブラウザなどの高価な機能のコードは機能モジュールに分割し、必要なときに読み込みます。この方法を使うことで、子プロセスに読み込まれる .dex サイズを 75% 減らし、最大 2.5 MB にすることができました。その結果、起動が速くなり、メモリ使用量も減りました。
このアーキテクチャによって、ブラウザ プロセスの最適化も可能になります。アプリケーションの初期化中にブラウザ プロセスのコードの大部分をバックグラウンド スレッドでプリロードした場合も起動時間を短縮でき、読み込み時間が 7.6% 高速になりました。ブラウザのコードが必要なアクティビティなどのコンポーネントが起動するときには、すでに読み込みが終わっています。機能モジュールへの機能の割り当てを最適化すると、オンデマンドで機能を読み込むことができます。これにより、機能が実際に使われるまで、メモリや読み込みのコストを節約できます。
結果
M89 で Chrome に Isolated Splits が搭載されて以来、数か月にわたる実際のデータが蓄積されており、Android Oreo 以降を実行しているすべての Android ユーザーの Chrome で、メモリ使用量、起動時間、ページ読み込みのスピード、安定性が大きく改善されたことがわかりました。
- 合計メモリ使用量の中央値が 5.2% 改善
- レンダラー プロセスのメモリ使用量の中央値が 7.9% 改善
- GPU プロセスのメモリ使用量の中央値が 7.6% 改善
- ブラウザ プロセスのメモリ使用量の中央値が 1.2% 改善
- 起動時間の 95 パーセンタイルが 7.6% 改善
- ページ読み込みスピードの 95 パーセンタイルが 2.3% 改善
- ブラウザのクラッシュ率とレンダラーのハング率の両方が大幅に改善
すべての統計情報の出典 : Chrome クライアントから匿名で集計した実データ。
Reviewed by Eiji Kitamura – Developer Relations Team<!—->