事例紹介: Gmail Wear OS チームがアプリの起動を 50% 改善した方法

「アプリの起動」は、ユーザーに対するアプリの第一印象を表します。また、ユーザーは待ち時間を嫌いなので、アプリがすばやく起動するようにする必要があります。ここでは、実際のアプリ開発チームがアプリの起動に関する問題をどのように発見し、診断したかを紹介します。ここでは、Gmail Wear OS チームが行った作業をご紹介します。

Gmail Wear OS チームは、チームのアプリ パフォーマンス基準を満たすために、アプリの起動とランタイムのレンダリングのパフォーマンスに特に重点を置いて最適化に取り組みました。ただし、ターゲットとする特定のしきい値がない場合でも、時間をかけて調査することで、アプリの起動を改善する余地はほとんど常にあります。

トレースをキャプチャしてアプリの起動を確認する

分析を開始するには、アプリの起動を含むトレースをキャプチャし、Perfetto または Android Studio で詳しく調査します。このケーススタディでは、アプリだけでなくデバイス システム全体で何が起こっているかを示すため、Perfetto を使用しています。Perfetto にトレースをアップロードすると、次のようになります。

図 1. Perfetto でのトレースの初期ビュー。

ここではアプリの起動を改善することに重点を置いているため、Android App Startups カスタム指標のある行を見つけます。その行にカーソルを合わせると表示される固定アイコン をクリックして、ビューの上部に固定すると便利です。[Android App Startups] 行のバーまたはスライスは、アプリの起動が最初のアプリフレームが画面に描画されるまでにかかった時間の範囲を示すため、そこで問題やボトルネックを探す必要があります。

固定するオプションがハイライト表示された Android App Startups 行。
図 2. Android App Startups カスタム指標をダッシュボードの上部に固定すると、分析が容易になります。

Android アプリの起動指標は、reportFullyDrawn() を使用している場合でも初期表示までの時間を表します。完全表示までの時間を特定するには、Perfetto の検索ボックスで reportFullyDrawn() を検索します。

メインスレッドを確認する

まず、メインスレッドで何が起こっているかを調べます。メインスレッドは通常、すべての UI レンダリングが行われる場所であるため、非常に重要です。メインスレッドがブロックされると、描画は行われず、アプリがフリーズしているように見えます。そのため、長時間実行オペレーションがメインスレッドで発生していないことを確認する必要があります。

メインスレッドを見つけるには、アプリのパッケージ名を含む行を探して開きます。パッケージと同じ名前の 2 つの行(通常はセクションの最初の 2 行)がメインスレッドを表します。2 つのメインスレッド行のうち、1 行目は CPU の状態を表し、2 行目はトレースポイントを表します。2 つのメインスレッド行を Android アプリの起動指標の下に固定します。

Android アプリの起動とメインスレッドの行が固定されています。
図 3. Android App Startups カスタム指標の下にメインスレッドの行を固定し、分析に役立てることができます。

実行可能な状態で費やされた時間と CPU の競合

アプリ起動時の CPU アクティビティの集計ビューを取得するには、メインスレッド上にカーソルをドラッグして、アプリの起動時間の範囲をキャプチャします。表示される [Thread States] パネルに、選択した期間内で各 CPU 状態で費やされた合計時間が表示されます。

Runnable 状態で費やした時間を確認します。スレッドが Runnable 状態の場合、スレッドは処理を実行できますが、処理はスケジュールされません。これは、デバイスに高負荷があり、優先度の高いタスクのスケジュールを設定できないことを示している可能性があります。一番上にある、ユーザーに表示されるアプリはスケジューリングの優先度が最も高いため、アイドル状態のメインスレッドは、多くの場合、アニメーションのレンダリングなど、アプリ内の集中的なプロセスが CPU 時間についてメインスレッドと競合していることを示します。

[スレッドの状態] パネルで、さまざまな状態の合計時間とともにハイライト表示されたメインスレッド。
図 4. Runnable 状態から Running 状態までの相対時間を評価して、CPU 競合の量を大まかに把握します。

Runnable 状態と Running 状態の時間の比率が高いほど、CPU 競合が発生している可能性が高くなります。この方法でパフォーマンスの問題を検査する場合は、まず最も実行時間の長いフレームに注目し、より短いフレームに移ります。

Runnable 状態で費やされた時間を分析する場合は、デバイスのハードウェアを考慮してください。 このアプリは 2 つの CPU を搭載したウェアラブル デバイスで実行されているため、CPU の数が多いデバイスの場合よりも、Runnable 状態で費やされる時間と、他のプロセスとの CPU 競合が増えることが期待されます。Runnable 状態で費やされる時間は、一般的なスマートフォン アプリで想定した時間より長くても、ウェアラブルの場合、これは理解できる場合があります。

OpenDexFilesFromOat*にかかった時間

次に、OpenDexFilesFromOat* にかかった時間を確認します。トレースでは、bindApplication スライスと同時に発生します。このスライスは、アプリケーションの DEX ファイルの読み取りに要した時間を表します。

バインダー トランザクションのブロック

次に、バインダー トランザクションを確認します。Binder トランザクションは、クライアントとサーバー間の呼び出しを表します。この場合、アプリ(クライアント)は binder transaction を使用して Android システム(サーバー)を呼び出し、サーバーは binder reply で応答します。起動時にアプリが不要なバインダー トランザクションを行わないようにします。これは、CPU 競合のリスクが高まるためです。可能であれば、アプリの起動後にバインダー呼び出しを伴う作業を遅らせます。バインダー トランザクションを行う必要がある場合は、デバイスの Vsync リフレッシュ レートを超えないようにしてください。

この場合、最初のバインダー トランザクションは通常 ActivityThreadMain スライスと同時に発生するため、非常に長いように見えます。何が起きているのかを詳しく調べるには、次の手順を行います。

  1. 関連するバインダの返信を表示し、バインダ トランザクションの優先度の詳細を確認するには、目的のバインダ トランザクション スライスをクリックします。
  2. バインダの返信を表示するには、[現在の選択] パネルに移動し、[フォロー中のスレッド] セクションの [バインダの返信] をクリックします。[Thread] フィールドは、手動で移動する場合にバインダー応答が発生するスレッド(別のプロセスにあるスレッド)も示します。バインダ トランザクションを接続して応答する行が表示されます。

    バインダー呼び出しと返信をつなぐ線が接続されています。
    図 5. アプリの起動中に発生しているバインダー トランザクションを特定し、それらを延期できるかどうかを評価します。
  3. システム サーバーがこのバインダ トランザクションをどのように処理しているかを確認するには、Cpu 0Cpu 1 のスレッドを画面上部に固定します。

  4. バインダ応答を処理するシステム プロセスを見つけるために、バインダ応答スレッド名(この場合は「Binder:687_11 [2542]」)を含むスライスを見つけます。関連するシステム プロセスをクリックして、バインダー トランザクションに関する詳細情報を取得します。

CPU 0 で発生する対象のバインダー トランザクションに関連する次のシステム プロセスを見てみましょう。

終了状態が実行可能(プリエンプト)のシステム プロセス。
図 6. システム プロセスの状態は Runnable (Preempted) で、遅延が発生していることを示しています。

[終了状態] は「Runnable (Preempted)」です。これは、CPU が他の処理を行っているためにプロセスが遅延していることを意味します。プリエンプトされる要因を確認するには、[Ftrace Events] 行を開きます。利用可能になった [Ftrace Events] タブで、スクロールして目的のバインダー スレッド「Binder:687_11 [2542]」に関連するイベントを探します。システム プロセスがプリエンプトされる頃に、引数「decon」を含む 2 つのシステム サーバー イベントが発生しました。これは、ディスプレイ コントローラに関連していることを意味します。ディスプレイ コントローラがフレームを画面に配置する重要なタスクであるため、これは理にかなっています。イベントはそのままにします。

ハイライト表示されたバインダー トランザクションに関連付けられた FTrace イベント。
図 7. FTrace イベントは、ディスプレイ コントローラ イベントによってバインダー トランザクションが遅延していることを示します。

JIT アクティビティ

ジャストインタイム コンパイル(JIT)アクティビティを調査するには、アプリに属するプロセスを開き、2 つの「Jit スレッドプール」行を見つけて、ビューの上部に固定します。このアプリではアプリの起動時にベースライン プロファイルを活用しているため、最初の Choreographer.doFrame 呼び出しの終了によって示される最初のフレームが描画されるまで、JIT アクティビティはほとんど発生しません。ただし、起動が遅い理由 JIT compiling void に注意してください。これは、Application creation というラベルの付いたトレースポイントで発生したシステム アクティビティが原因で、多くのバックグラウンド JIT アクティビティが発生していることを示しています。この問題を解決するには、プロファイル コレクションをアプリを使用する準備ができるまで拡張して、最初のフレームがベースライン プロファイルに描画された直後に発生するイベントを追加します。多くの場合、これを行うには、ベースライン プロファイル コレクションの Macrobenchmark テストの最後に、特定の UI ウィジェットが画面に表示されるのを待ち、画面が完全に入力されるのを待機する行を追加します。

「Jit compiling void」スライスがハイライト表示された Jit スレッドプール。
図 8. JIT アクティビティが多数表示される場合は、アプリを使用する準備ができるまでベースライン プロファイルを展開します。

結果

この分析の結果、Gmail Wear OS チームは以下の改善を行いました。

  • 同社はアプリの起動中に CPU アクティビティを確認した際に競合に気づいたため、アプリが 1 枚の静止画像で読み込み中であることを示すためのスピナー アニメーションを置き換えました。また、シマー状態(アプリが読み込み中であることを示すために使用される 2 つ目の画面状態)を遅延させて、CPU リソースを解放するためにスプラッシュ画面が長くなりました。これにより、アプリの起動レイテンシが 50% 改善されました。
  • OpenDexFilesFromOat*JIT アクティビティにかかった時間から、ベースライン プロファイルR8 の書き換えが可能になりました。これにより、アプリの起動レイテンシが 20% 改善されました。

ここでは、アプリのパフォーマンスを効率的に分析するための、チームによるヒントをいくつかご紹介します。

  • トレースと結果を自動的に収集できる継続的なプロセスを設定します。ベンチマークを使用して、アプリの自動トレースをセットアップすることを検討してください。
  • A/B テストを使用して、改善の余地があると思われる変更は拒否し、改善しない場合は却下します。Macrobenchmark ライブラリを使用すると、さまざまなシナリオでパフォーマンスを測定できます。

詳細については、次のリソースをご覧ください。