プロファイリング
プロファイリングとは、アプリのパフォーマンス、リソース使用量、動作を分析して、潜在的なボトルネックや非効率な点を特定するプロセスです。プロファイリングツールを活用して、アプリがさまざまなデバイスや条件下でスムーズに動作することを確認する価値があります。
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で開くと、次のようなものが表示されます。
WASDキーを使って、平行移動やズームができます。
UIはツールによって多少異なる場合がありますが、以下の手順は使用しているツールに関わらず適用できます。
画面右上のこのチェックボックスをオンにすると、16msのフレーム境界がハイライトされます。
上記のスクリーンショットのように、ゼブラ模様が表示されるはずです。表示されない場合は、別のデバイスでプロファイリングを試してください。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になります。このスレッドで見られるイベントは、
Choreographer
、traversals
、DispatchUI
に関連するもののはずです。 -
JSスレッド ここでJavaScriptが実行されます。スレッド名は、デバイスのカーネルの協力度合いによって
mqt_js
または<...>
のいずれかになります。名前がない場合に特定するには、JSCall
、Bridge.executeJSCall
などを探してください。 -
ネイティブモジュールスレッド ここでネイティブモジュールの呼び出し(例:
UIManager
)が実行されます。スレッド名はmqt_native_modules
または<...>
のいずれかになります。後者の場合に特定するには、NativeCall
、callJavaModuleMethod
、onBatchComplete
などを探してください。 -
おまけ:Render Thread Android L (5.0)以降を使用している場合、アプリケーションにはRender Threadもあります。このスレッドは、UIを描画するために使用される実際のOpenGLコマンドを生成します。スレッド名は
RenderThread
または<...>
のいずれかになります。後者の場合に特定するには、DrawFrame
やqueueBuffer
などを探してください。
原因の特定
スムーズなアニメーションは次のように見えるはずです。
色の変化はそれぞれが1フレームです。フレームを表示するためには、すべてのUI作業がその16msの期間内に完了する必要があることを覚えておいてください。どのスレッドもフレームの境界近くで作業していないことに注目してください。このようにレンダリングしているアプリケーションは60 FPSでレンダリングされています。
しかし、カクつきに気づいた場合は、次のようなものが見えるかもしれません。
JSスレッドがほぼ常に、そしてフレームの境界を越えて実行されていることに注目してください!このアプリは60 FPSでレンダリングされていません。この場合、問題はJSにあります。
また、次のようなものが見えるかもしれません。
この場合、フレーム境界を越えて作業しているのはUIスレッドとRender Threadです。各フレームでレンダリングしようとしているUIが、あまりにも多くの作業を要求しています。この場合、問題はレンダリングされているネイティブビューにあります。
この時点で、次のステップに進むための非常に役立つ情報が得られたことになります。
JavaScriptの問題を解決する
JSの問題を特定した場合、実行している特定のJSに手がかりを探してください。上記のシナリオでは、RCTEventEmitter
がフレームごとに複数回呼び出されているのがわかります。以下は、上記のトレースからJSスレッドを拡大したものです。
これは正しくないようです。なぜこんなに頻繁に呼び出されるのでしょうか?それらは実際に異なるイベントなのでしょうか?これらの質問への答えは、おそらくあなたのプロダクトコードに依存します。そして多くの場合、shouldComponentUpdateを調べることになるでしょう。
ネイティブUIの問題を解決する
ネイティブUIの問題を特定した場合、通常は2つのシナリオがあります。
- 各フレームで描画しようとしているUIがGPUに過剰な作業をさせているか、
- アニメーションやインタラクション中に新しいUIを構築している(例:スクロール中に新しいコンテンツを読み込む)。
GPUの作業が多すぎる
最初のシナリオでは、UIスレッドやRender Threadが次のように見えるトレースが表示されます。
DrawFrame
に費やされる時間が長く、フレームの境界を越えていることに注目してください。これは、GPUが前のフレームからのコマンドバッファを空にするのを待っている時間です。
これを軽減するには、以下のことを行うべきです。
- アニメーションや変換が行われる複雑で静的なコンテンツに対して
renderToHardwareTextureAndroid
の使用を検討する(例:Navigator
のスライド/アルファアニメーション)。 - ほとんどの場合、フレームごとのGPUへの負荷を大幅に増加させるため、デフォルトで無効になっている
needsOffscreenAlphaCompositing
を使用していないことを確認してください。
UIスレッドで新しいビューを作成する
2番目のシナリオでは、次のようなものが表示されます。
まずJSスレッドが少し考え、次にネイティブモジュールスレッドで何らかの作業が行われ、その後UIスレッドでコストのかかるトラバーサルが行われていることに注目してください。
インタラクションが終わるまで新しいUIの作成を延期できるか、作成するUIを簡素化できる場合を除き、これを軽減する簡単な方法はありません。React Nativeチームは、メインスレッド外で新しいUIを作成・設定できるようにすることで、インタラクションをスムーズに継続させるためのインフラレベルの解決策に取り組んでいます。
ネイティブCPUのホットスポットを見つける
問題がネイティブ側にあると思われる場合は、CPUホットスポットプロファイラを使用して、何が起こっているかの詳細を取得できます。Android Studio Profilerパネルを開き、「Find CPU Hotspots (Java/Kotlin Method Recording)」を選択します。
「Find CPU Hotspots (Callstack Sample)」ではなく、「Find CPU Hotspots (Java/Kotlin Recording)」を選択してください。アイコンは似ていますが、機能が異なります。
インタラクションを実行し、「Stop recording」を押します。レコーディングはリソースを大量に消費するため、インタラクションは短くしてください。その後、結果のトレースをAndroid Studioで調査するか、エクスポートしてFirefox Profilerなどのオンラインツールで開くことができます。
システムトレースとは異なり、CPUホットスポットプロファイリングは低速なため、正確な測定値は得られません。しかし、どのネイティブメソッドが呼び出されているか、そして各フレームで時間がどこに比例して費やされているかのアイデアを与えてくれるはずです。