継続的インテグレーションでのベンチマーク

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

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

ファジーな結果

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

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

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

ベンチマークを実行する

CI パイプラインの一部としてベンチマークを実行するのは、Android Studio からローカルで実行するのとは異なります。一般的には、ローカルでは単一の Gradle タスク connectedCheck で Android 統合テストを実行します。このタスクでは、APK とテスト APK を自動的にビルドし、CI サーバーに接続されているデバイス上でテストを実行します。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 インストルメンテーション コマンドを実行します。

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

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

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

たとえば、dryRunMode 引数を設定すると、pull リクエスト検証プロセスの一部としてマイクロベンチマークを実行できます。このフラグを有効にすると、マイクロベンチマークは 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 が用意されています。これは、ユーザーに root 権限のあるデバイスにアクセスできる場合(「userdebug」ビルドなど)の安定性を確保するのに役立ちます。ライブラリのソースにある lockClocks.sh シェル スクリプトを使用して、これを複製できます。

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

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 でベンチマークを実行すると、出力ファイルは自動的にホスト コンピュータの build/outputs/connected_android_test_additional_output/debugAndroidTest/connected/ にある出力ディレクトリにコピーされます。

adb コマンドで直接実行する場合は、ファイルを手動で pull する必要があります。デフォルトで、レポートはテスト対象アプリの外部ストレージ(デバイス上)のメディア ディレクトリに保存されます。便宜性を考慮して、ライブラリはファイルのパスを 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 コマンドを使用します。このコマンドは、指定されたファイルをホスト上の現在のディレクトリに 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
        }
    ]
}

参考情報