Android の CPU プロファイラ

Android には大きく三種類の CPU プロファイラがある。 まず ART の VM が managed コード用に二つのプロファイラを持っている。 また NDK には simpleperf というネイティブコード主体のプロファイラがある。

この記事ではこれらのプロファイラをざっと眺め、それぞれの長所と短所について議論する。

ART のプロファイラ (Instrumentation)

ART のプロファイラには実装が二種類ある。 1つ目は昔からある Instrumentation Profiler. このプロファイラは VM が実行するコードの挙動を変えてメソッド呼び出しの出入り口を記録する。

データは Android Studio で様々にドリルダウンできるが、上のスクリーンショットにあるような時系列の “Flame chart” が一番とっつきやすい。

以下で議論する他のプロファイラは sampling based なのに対し、この Instrumentation Profiler は全てのメソッドを記録する。これは利点でもあり欠点でもある。

サンプリングと異なり実行時間の短い小さなメソッドを見逃さないので、細かいメソッド呼び出しが大量にある場合に混乱が少ない。そういう細かいメソッドがオーバーヘッドになることは、Java ではよくある。

欠点はオーバーヘッドの大きさ。「全てのメソッド呼び出し」は、すごく多い。記録の必要なデータも大きくなるためバッファがすぐに溢れるし、測定したいシナリオを実行するのも苦労するくらい実行も遅くなる。遅くなるだけでなく、遅さの内訳もしばしば大きく歪む: 本来それほど遅くないものが、すごく遅いように報告されることがある。

実行時間の歪みはオーバーヘッドのせいだけでなく、Instrumentation Profiling 利用時はコンパイラの最適化がほぼ無効になってしまうためでもある。プロファイラを使いたい類のタイトなコードの測定にとってこれは嬉しくない。

ART のプロファイラ (Sampling)

ART は Sampling Profiler もサポートしている。 サンプリングプロファイラは実行時にサンプリング用のスレッドを起動し、指定されたサンプリング間隔 (たとえば 10ms) で全 managed スレッドのスタックをサンプルする。

実行オーバーヘッドの少なさが Instrumentation Profiler に対する Sampling Profiler の利点。記録に必要なデータ量も相対的に小さいし、コンパイラの挙動に影響を与えることもない。

欠点は sampling に伴うデータの欠落。たとえば散弾的におこる描画のコマ落ちをプロファイルしたいとき、その遅いフレームが 100ms だとしても一番短い 1ms のサンプリング間隔ですら 100 サンプルしかとれない。ある程度は役に立つが心もとない。問題のあるコードパスを無理やり何度も呼び出すなど、サンプルを集めるための工夫が必要。

上のスクリーンショットは Instrumentation Profiler と同じアプリの同じスレッドをキャプチャしている。サンプリング故のデータの薄さがよくわかると思う。じっさい Sampling Profiler の Flame Chart はデータの欠落が多すぎてまともに読めない。実行時間をドリルダウンできる tree view の方が重宝するだろう。

ART プロファイラ全般の利点と欠点

ART のプロファイラはどちらも Android Studio に統合されている。おかげで気軽に使えるのは simpleperf に対する大きな利点だ。

また Platform の API を使ってアプリ内から起動できるのも便利。Debug#startMethodTracing()Debug#startMethodTracingSampling() といった API を使うと興味のあるコードパスだけをプロファイルすることができる。プロファイルから余計な実行を省けるのでデータが読みやすくなるし、Instrumentation Profiler のバッファ溢れや遅さの問題をワークアラウンドするのにも使える。

たとえば興味のある(遅い)フレームの直前直後に Choreographer などで startMethodTracing()stopMethodTracing() を差し込めば問題の起きる瞬間だけをプロファイルできる。IDE 越しでは遅すぎて使い物にならなかった Instrumentation Profiler も API 越しだとたまに出番がある。

2つの ART プロファイラに共通する欠点は、ネイティブ (JNI) コードがまったく見えないこと。JNI の先にある関数の実行時間の内訳がわからないし、ART の外側でネイティブコードが作ったスレッドも観測できない。たとえば RenderThread は ART のプロファイルにはまったく現れない。

それに加え、Sampling Profiler は JNI メソッドをサンプルしそこなうことが多い。そのせいでネイティブコードのホットスポットを見落としてしまう。Android の platform API はネイティブコードを多用しているモジュールも多いので、怪しいときは Perfetto や後述する simpelperf と照らし合わせたほうが良い。なお Instrumentation Profiler に取りこぼしの問題はない。

Simpleperf

Simpleperf は NDK に付属するコマンドラインベースのプロファイラである。デバイス上の simpleperf コマンドとホスト側で動かす各種 Python スクリプトからなる。最近は Android Studio からも使うことができるらしい。(森田は試したことがない。) Android N 以降に対応する。

NDK 付属のためネイティブコード専用と思いがちだが、実際には managed コードもシームレスにプロファイルできる。ただし Android Studio との統合は限定的なので、Simpleperf の良さを引き出すための敷居は少し高い。

Simpleperf はプロファイル結果を Flamegraph として出力する。データは HTML に保存され、たとえば以下のような結果を得られる(HTML).

Simpleperf はバックエンドに Linux の perf コマンドと同じ perf_event_open() を使ってデータをサンプルする。パフォーマンスカウンタを使うため、ART の Sampling Profiler より一層オーバーヘッドが小さく、かつスケールする。(ART のサンプリングオーバーヘッドはプロセス内の ART スレッド数に比例するが、ハードウェアカウンタのオーバーヘッドは CPU 数に比例する。) managed コード対応など、Linux 付属の perf コマンドにない Android 固有の機能も多い。

Simpleperf には ART プロファイラにない利点が他にもいくつかある:

  • ネイティブコードと managed コードを同時にプロファイルできる。RenderThread や JIT スレッドのような maanged でないスレッドも見える。
  • 単一のプロセスだけでなく、システム全体のプロファイルをとることができる。アプリケーション開発者にとっては特に嬉しくないが、OS の中の人は重宝しているだろう。
  • アプリのプロセス全体だけでなく特定スレッドに絞ったプロファイルも取れる。プロファイルのバッファ消費量や可視化時のノイズを減らし興味のあるコードだけを重点的にプロファイルできて便利。

欠点もある。一番残念なのは API の不在。アプリからプロファイラを開始/停止できない…というか、できるが、大変めんどくさい。Manifest で特別なフラグを立て、かつ投げやりな Java のコード をプロジェクトにコピーして使う必要がある。Maven のパッケージのような甘えは許されていない。敷居が高すぎて、森田はまだ API アクセスを試したことがない。

データの後処理のやりにくさも敷居を高くしている。ネイティブコードのシンボル解決の都合で複雑にならざるを得ないのは理解できるが、ユーザフレンドリとは言えない。

Flamegraphs と Flame Charts

Simpleperf の Flamegraphs は CPU の使用時間を階層的に可視化する。一方で Android Studio の Flame Charts は関数呼び出しの入れ子関係を時系列で可視化する。

CPU のオーバーロードを減らしスループットを改善したいインフラやサーバサイドの分析では CPU 使用時間の内訳にフォーカスした Flamegraphs が有効。一方でレイテンシに注力するクライアントアプリの分析ではしばしば Flame Charts を重宝する。

Flamegraph の提唱者 Brendan Gregg のサイトによれば、Flame Charts はもともと WebKit がウェブ開発者向け Web Inspector のプロファイリング機能として導入したという。

関連リンク

Ack

一連の草稿に目を通して感想をくれた皆様ありがとう: Jun Mukai, Kenichi Ishibashi, Kazuyoshi Kato, karino2, Yuichi Tateno, Kensuke Nagae, Hiroshi Kurokawa