メインコンテンツへスキップ

プロファイリング

プロファイリングとは、アプリのパフォーマンス、リソース使用量、動作を分析して、潜在的なボトルネックや非効率な点を特定するプロセスです。プロファイリングツールを活用して、アプリがさまざまなデバイスや条件下でスムーズに動作することを確認する価値があります。

iOSではInstrumentsが非常に貴重なツールであり、AndroidではAndroid Studio Profilerの使い方を学ぶべきです。

しかし、まず最初に、開発モードがオフになっていることを確認してください!アプリケーションのログに__DEV__ === false, development-level warning are OFF, performance optimizations are ONと表示されるはずです。

システムトレースによるAndroid UIパフォーマンスのプロファイリング

Androidは1万種類以上の異なるスマートフォンをサポートしており、ソフトウェアレンダリングをサポートするように一般化されています。フレームワークのアーキテクチャと多くのハードウェアターゲットにわたって一般化する必要があるため、残念ながらiOSに比べて無料で得られるものは少なくなります。しかし、時には改善できることもありますし、多くの場合、それはネイティブコードのせいではありません。

このジャンク(カクつき)をデバッグするための最初のステップは、各16ミリ秒のフレーム中に時間がどこで費やされているかという根本的な問いに答えることです。そのために、Android Studioに組み込まれているシステムトレースプロファイラを使用します。

1. トレースの収集

まず、調査したいカクつきが発生するデバイスをUSBでコンピュータに接続します。プロジェクトのandroidフォルダをAndroid Studioで開き、右上のペインでデバイスを選択し、プロジェクトをプロファイラブルとして実行します。

アプリがプロファイラブルとしてビルドされ、デバイスで実行されたら、プロファイルしたいナビゲーションやアニメーションの直前の状態までアプリを進め、Android StudioのProfilerペインで「Capture System Activities」タスクを開始します。

トレースの収集が始まったら、対象のアニメーションやインタラクションを実行します。その後、「Stop recording」を押します。これで、Android Studioで直接トレースを調査できます。または、「Past Recordings」ペインでそれを選択し、「Export recording」を押して、Perfettoのようなツールで開くこともできます。

2. トレースの読み取り

トレースをAndroid StudioまたはPerfettoで開くと、次のようなものが表示されます。

Example

ヒント

WASDキーを使って、平行移動やズームができます。

UIはツールによって多少異なる場合がありますが、以下の手順は使用しているツールに関わらず適用できます。

VSyncハイライトを有効にする

画面右上のこのチェックボックスをオンにすると、16msのフレーム境界がハイライトされます。

Enable VSync Highlighting

上記のスクリーンショットのように、ゼブラ模様が表示されるはずです。表示されない場合は、別のデバイスでプロファイリングを試してください。Samsungはvsyncの表示に問題があることが知られていますが、Nexusシリーズは一般的にかなり信頼できます。

3. プロセスを見つける

パッケージ名の一部が見えるまでスクロールしてください。この例では、com.facebook.adsmanagerをプロファイリングしていましたが、カーネルのスレッド名の制限のため、book.adsmanagerと表示されています。

左側には、右側のタイムライン行に対応するスレッドのセットが表示されます。私たちの目的のために重要なスレッドがいくつかあります。UIスレッド(パッケージ名またはUI Threadという名前)、mqt_js、そしてmqt_native_modulesです。Android 5以降で実行している場合は、Render Threadも重要です。

  • UIスレッド これは、標準的なAndroidのmeasure/layout/drawが行われる場所です。右側のスレッド名はパッケージ名(この例ではbook.adsmanager)またはUI Threadになります。このスレッドで見られるイベントは、ChoreographertraversalsDispatchUIに関連するもののはずです。

    UI Thread Example

  • JSスレッド ここでJavaScriptが実行されます。スレッド名は、デバイスのカーネルの協力度合いによってmqt_jsまたは<...>のいずれかになります。名前がない場合に特定するには、JSCallBridge.executeJSCallなどを探してください。

    JS Thread Example

  • ネイティブモジュールスレッド ここでネイティブモジュールの呼び出し(例:UIManager)が実行されます。スレッド名はmqt_native_modulesまたは<...>のいずれかになります。後者の場合に特定するには、NativeCallcallJavaModuleMethodonBatchCompleteなどを探してください。

    Native Modules Thread Example

  • おまけ:Render Thread Android L (5.0)以降を使用している場合、アプリケーションにはRender Threadもあります。このスレッドは、UIを描画するために使用される実際のOpenGLコマンドを生成します。スレッド名はRenderThreadまたは<...>のいずれかになります。後者の場合に特定するには、DrawFramequeueBufferなどを探してください。

    Render Thread Example

原因の特定

スムーズなアニメーションは次のように見えるはずです。

Smooth Animation

色の変化はそれぞれが1フレームです。フレームを表示するためには、すべてのUI作業がその16msの期間内に完了する必要があることを覚えておいてください。どのスレッドもフレームの境界近くで作業していないことに注目してください。このようにレンダリングしているアプリケーションは60 FPSでレンダリングされています。

しかし、カクつきに気づいた場合は、次のようなものが見えるかもしれません。

Choppy Animation from JS

JSスレッドがほぼ常に、そしてフレームの境界を越えて実行されていることに注目してください!このアプリは60 FPSでレンダリングされていません。この場合、問題はJSにあります

また、次のようなものが見えるかもしれません。

Choppy Animation from UI

この場合、フレーム境界を越えて作業しているのはUIスレッドとRender Threadです。各フレームでレンダリングしようとしているUIが、あまりにも多くの作業を要求しています。この場合、問題はレンダリングされているネイティブビューにあります

この時点で、次のステップに進むための非常に役立つ情報が得られたことになります。

JavaScriptの問題を解決する

JSの問題を特定した場合、実行している特定のJSに手がかりを探してください。上記のシナリオでは、RCTEventEmitterがフレームごとに複数回呼び出されているのがわかります。以下は、上記のトレースからJSスレッドを拡大したものです。

Too much JS

これは正しくないようです。なぜこんなに頻繁に呼び出されるのでしょうか?それらは実際に異なるイベントなのでしょうか?これらの質問への答えは、おそらくあなたのプロダクトコードに依存します。そして多くの場合、shouldComponentUpdateを調べることになるでしょう。

ネイティブUIの問題を解決する

ネイティブUIの問題を特定した場合、通常は2つのシナリオがあります。

  1. 各フレームで描画しようとしているUIがGPUに過剰な作業をさせているか、
  2. アニメーションやインタラクション中に新しいUIを構築している(例:スクロール中に新しいコンテンツを読み込む)。

GPUの作業が多すぎる

最初のシナリオでは、UIスレッドやRender Threadが次のように見えるトレースが表示されます。

Overloaded GPU

DrawFrameに費やされる時間が長く、フレームの境界を越えていることに注目してください。これは、GPUが前のフレームからのコマンドバッファを空にするのを待っている時間です。

これを軽減するには、以下のことを行うべきです。

  • アニメーションや変換が行われる複雑で静的なコンテンツに対してrenderToHardwareTextureAndroidの使用を検討する(例:Navigatorのスライド/アルファアニメーション)。
  • ほとんどの場合、フレームごとのGPUへの負荷を大幅に増加させるため、デフォルトで無効になっているneedsOffscreenAlphaCompositing使用していないことを確認してください。

UIスレッドで新しいビューを作成する

2番目のシナリオでは、次のようなものが表示されます。

Creating Views

まずJSスレッドが少し考え、次にネイティブモジュールスレッドで何らかの作業が行われ、その後UIスレッドでコストのかかるトラバーサルが行われていることに注目してください。

インタラクションが終わるまで新しいUIの作成を延期できるか、作成するUIを簡素化できる場合を除き、これを軽減する簡単な方法はありません。React Nativeチームは、メインスレッド外で新しいUIを作成・設定できるようにすることで、インタラクションをスムーズに継続させるためのインフラレベルの解決策に取り組んでいます。

ネイティブCPUのホットスポットを見つける

問題がネイティブ側にあると思われる場合は、CPUホットスポットプロファイラを使用して、何が起こっているかの詳細を取得できます。Android Studio Profilerパネルを開き、「Find CPU Hotspots (Java/Kotlin Method Recording)」を選択します。

Java/Kotlinレコーディングを選択する

「Find CPU Hotspots (Callstack Sample)」ではなく、「Find CPU Hotspots (Java/Kotlin Recording)」を選択してください。アイコンは似ていますが、機能が異なります。

インタラクションを実行し、「Stop recording」を押します。レコーディングはリソースを大量に消費するため、インタラクションは短くしてください。その後、結果のトレースをAndroid Studioで調査するか、エクスポートしてFirefox Profilerなどのオンラインツールで開くことができます。

システムトレースとは異なり、CPUホットスポットプロファイリングは低速なため、正確な測定値は得られません。しかし、どのネイティブメソッドが呼び出されているか、そして各フレームで時間がどこに比例して費やされているかのアイデアを与えてくれるはずです。