継続的インテグレーションでベンチマークを実行する

継続的インテグレーション(CI)でベンチマークを実行すると、アプリのリリース前であってもパフォーマンスの推移をトラッキングしてパフォーマンスの低下(または向上)を把握することができます。このページでは、CI でのベンチマークに関する基本情報を提供します。

CI でベンチマークを開始する前に、結果のキャプチャと評価が通常のテストとどのような点で異なるかを考慮してください。

ファジーな結果

ベンチマークはインストルメンテーション テストですが、結果は単なる合格 / 不合格ではありません。ベンチマークは、それらが実行された特定のデバイスの時間測定値を提供します。経時的な結果をグラフ化すると、測定システムで変化をモニタリングしてノイズを観測できます。

実際のデバイスを使用する

ベンチマークは、実際の Android デバイス上で実行する必要があります。エミュレータ上でも実行できますが、そうしないことを強くおすすめします。結果が実際のユーザー エクスペリエンスを表すものにならず、ホストの OS とハードウェアに機能に結び付いた測定値が生成されるからです。実際のデバイスか、または実際のデバイスでテストを実行できるサービス(Firebase Test Lab など)の使用を検討してください。

ベンチマークを実行する

CI パイプラインの一部としてベンチマークを実行するのは、Android Studio からローカルで実行するのとは異なります。通常、ローカルでは、単一の Gradle タスク connectedCheck で Android 統合テストを実行します。このタスクは、APK とテスト APK を自動的にビルドし、現在接続されているデバイス上でテストを実行します。CI で実行する場合、通常はこのフローを別個のフェーズに分割する必要があります。

ビルド

Microbenchmark ライブラリの場合は、Gradle タスク assemble[VariantName]AndroidTest を実行します。このタスクは、アプリのコードとテスト対象コードの両方を含むテスト APK を作成します。

一方、Macrobenchmark ライブラリの場合は、ターゲット APK とテスト APK を別々にビルドすることが求められます。したがって、Gradle タスク :app:assemble[VariantName] および :macrobenchmark:assemble[VariantName] を実行します。

インストールと実行

一般的に、これらのステップは Gradle タスクを実行しなくても実施できます。なお、実際のデバイスでテストを実行できるサービスを使用するかどうかによって、これらが抽象化される場合があります。

インストールするには、adb install コマンドを使用し、テスト APK(またはターゲット APK)を指定します。

すべてのベンチマークを実行するには、adb shell am instrument コマンドを実行します。

adb shell am instrument -w com.example.benchmark/androidx.benchmark.junit4.AndroidBenchmarkRunner

なお、Macrobenchmark ライブラリを使用する場合は、通常の androidx.test.runner.AndroidJUnitRunner をインストルメンテーション ランナーとして使用します。

-e 引数を使用すると、Gradle 構成の場合と同じインストルメンテーション引数を渡すことができます。すべてのインストルメンテーション引数オプションを確認するには、Microbenchmark または Macrobenchmark のページをご覧ください。

たとえば、dryRunMode 引数を設定すると、プルリクエスト検証プロセスの一部としてマイクロベンチマークを実行できます。このフラグを有効にすると、マイクロベンチマークは 1 回のループでのみ実行されます。これにより、実行に時間がかかりすぎないようにして、マイクロベンチマークが正しく実行されていることを確認できます。

adb shell am instrument -w -e "androidx.benchmark.dryRunMode.enable" "true" com.example.benchmark/androidx.benchmark.junit4.AndroidBenchmarkRunner

コマンドラインからインストルメンテーション テストを実行する方法について詳しくは、adb を使用してテストを実行するをご覧ください。

クロックをロックする

Microbenchmark Gradle プラグインには、ユーザーに root 権限のあるデバイスの CPU クロックをロックするためのコマンド ./gradlew lockClocks が用意されています。これは、「userdebug」ビルドなど、ユーザーに root 権限のあるデバイスにアクセスできる場合に安定性を確保するために役立ちます。ライブラリのソースにある lockClocks.sh シェル スクリプトを使用して、これを複製できます。

Linux または Mac のホストからスクリプトを直接実行することも、いくつかの adb コマンドを使用してデバイスにプッシュすることもできます。

adb push path/lockClocks.sh /data/local/tmp/lockClocks.sh
adb shell /data/local/tmp/lockClocks.sh
adb shell rm /data/local/tmp/lockClocks.sh

シェル スクリプトをホスト上で直接実行すると、接続されたデバイスにこれらのコマンドがディスパッチされます。

CPU クロックをロックするメリットについて詳しくは、一貫性のあるベンチマークを取得する方法をご確認ください。

結果を収集する

ベンチマーク ライブラリは、JSON で測定値を出力するとともに、各ベンチマークの実行後に Android デバイス上のディレクトリにプロファイリング トレースを出力します。Macrobenchmark ライブラリは複数の perfetto トレース ファイルを出力します。これらのファイルは、個々の MacrobenchmarkRule.measureRepeated ループの測定される反復処理ごとに 1 つずつ作成されます。一方、Microbenchmark は、個々の BenchmarkRule.measureRepeated のすべての反復処理について 1 つだけトレース ファイルを作成します。プロファイリング トレース ファイルも、これと同じディレクトリに出力されます。

ファイルの保存と検索

Gradle でベンチマークを実行すると、出力ファイルは自動的にホストデバイスにコピーされます。adb コマンドで直接実行する場合は、手動でそれらを取得する必要があります。デフォルトでは、レポートはデバイス上でテスト対象アプリの外部ストレージのメディア ディレクトリに保存されます。便宜性を考慮して、ライブラリはファイルのパスを Logcat に出力します。なお、出力フォルダは、ベンチマークを実行している Android バージョンによって異なります。

Benchmark: writing results to /storage/emulated/0/Android/media/com.example.macrobenchmark/com.example.macrobenchmark-benchmarkData.json

インストルメンテーション引数 additionalTestOutputDir を使用して、デバイス上のベンチマーク レポートの保存場所を構成することもできます。このフォルダは、アプリによる書き込みが可能なフォルダでなければなりません。

adb shell am instrument -w -e additionalTestOutputDir /sdcard/Download/ com.example.benchmark/androidx.benchmark.junit4.AndroidBenchmarkRunner

Android 10(API レベル 29)以降では、アプリのテストはデフォルトではストレージ サンドボックス内で実行されます。したがって、アプリはアプリ固有のディレクトリ外のファイルにはアクセスできません。いくつかのグローバル ディレクトリ(/sdcard/Download など)に保存できるようにするには、次のインストルメンテーション引数を渡します。

-e no-isolated-storage true

また、ベンチマークのマニフェストで、レガシー ストレージ オプションを明示的に許可する必要もあります。

<application android:requestLegacyExternalStorage="true" ... >

詳しくは、対象範囲別ストレージを一時的にオプトアウトするをご覧ください。

ファイルの取得

生成されたファイルをデバイスから取得するには、adb pull コマンドを使用できます。このコマンドは、指定されたファイルをホスト上の現在のディレクトリにプルします。

adb pull /storage/emulated/0/Android/media/com.example.macrobenchmark/com.example.macrobenchmark-benchmarkData.json

指定されたフォルダからすべての benchmarkData を取得するには、次のスニペットをご確認ください。

# The following command pulls all files ending in -benchmarkData.json from the directory
# hierarchy starting at the root /storage/emulated/0/Android.
adb shell find /sdcard/Download -name "*-benchmarkData.json" | tr -d '\r' | xargs -n1 adb pull

なお、トレース ファイル(.trace または .perfetto-trace)は benchmarkData.json と同じフォルダに保存されるので、同じ方法で収集できます。

ベンチマーク データの例

ベンチマーク ライブラリは、ベンチマークを実行したデバイスと実行した実際のベンチマークに関する情報を含む JSON ファイルを生成します。次のスニペットは、生成された JSON ファイルを表しています。

{
    "context": {
        "build": {
            "brand": "google",
            "device": "blueline",
            "fingerprint": "google/blueline/blueline:12/SP1A.210812.015/7679548:user/release-keys",
            "model": "Pixel 3",
            "version": {
                "sdk": 31
            }
        },
        "cpuCoreCount": 8,
        "cpuLocked": false,
        "cpuMaxFreqHz": 2803200000,
        "memTotalBytes": 3753299968,
        "sustainedPerformanceModeEnabled": false
    },
    "benchmarks": [
        {
            "name": "startup",
            "params": {},
            "className": "com.example.macrobenchmark.startup.SampleStartupBenchmark",
            "totalRunTimeNs": 4975598256,
            "metrics": {
                "timeToInitialDisplayMs": {
                    "minimum": 347.881076,
                    "maximum": 347.881076,
                    "median": 347.881076,
                    "runs": [
                        347.881076
                    ]
                }
            },
            "sampledMetrics": {},
            "warmupIterations": 0,
            "repeatIterations": 3,
            "thermalThrottleSleepSeconds": 0
        }
    ]
}

参考情報