Jetpack Macrobenchmark でユースケースのベンチマークを行う

Macrobenchmark を使用すると、Android M(API 23)以降を搭載したデバイスで、アプリの起動および実行時パフォーマンスに関するテストを直接作成できます。

Macrobenchmark は、Android Studio の最新バージョン(2021.1.1 以降)で使用することをおすすめします。このバージョンの IDE は、Macrobenchmark を統合する新機能を備えているからです。以前のバージョンの Android Studio を使用している場合は、このトピックの後半で説明する追加の手順を使用して、トレース ファイルを操作できます。

ベンチマーク テストは、Macrobenchmark ライブラリの MacrobenchmarkRule JUnit4 ルール API によって提供されます。

Kotlin

    @get:Rule
    val benchmarkRule = MacrobenchmarkRule()

    @Test
    fun startup() = benchmarkRule.measureRepeated(
        packageName = "mypackage.myapp",
        metrics = listOf(StartupTimingMetric()),
        iterations = 5,
        startupMode = StartupMode.COLD
    ) { // this = MacrobenchmarkScope
        pressHome()
        val intent = Intent()
        intent.setPackage("mypackage.myapp")
        intent.setAction("mypackage.myapp.myaction")
        startActivityAndWait(intent)
    }
  

Java

    @Rule
    MacrobenchmarkRule benchmarkRule = MacrobenchmarkRule()

    @Test
    void startup() = benchmarkRule.measureRepeated(
        "mypackage.myapp", // packageName
        listOf(StartupTimingMetric()), // metrics
        5, // iterations
        StartupMode.COLD // startupMode
    ) { scope ->
        scope.pressHome()
        Intent intent = Intent()
        intent.setPackage("mypackage.myapp")
        intent.setAction("mypackage.myapp.myaction")
        scope.startActivityAndWait(intent)
    }
  

指標は Android Studio に直接表示されます。また、JSON ファイルの CI 使用状況にも出力されます。

Studio の結果の例

モジュールの設定

マクロ ベンチマークには、アプリを測定するテストを実行するアプリコードとは別の com.android.test モジュールが必要です。

Bumblebee

Android Studio Bumblebee には、Macrobenchmark モジュールの設定を簡素化するテンプレートが用意されています。

新しいモジュールを追加する

ベンチマーク モジュール テンプレートを使用すると、アプリ モジュールによってビルドされたアプリを測定するためのモジュール(サンプル起動ベンチマークなど)が、プロジェクト内に自動的に作成されます。

モジュール テンプレートを使用して新しいモジュールを作成する手順は次のとおりです。

  1. Android Studio の [Project] パネルでプロジェクトまたはモジュールを右クリックして、[New] > [Module] をクリックします。

  2. [Benchmark Module] を選択します。

    ベンチマーク モジュール テンプレート

  3. ターゲット アプリ(ベンチマーク対象のアプリ)に加えて、新しい Macrobenchmark モジュールのパッケージ名とモジュール名をカスタマイズできます。

  4. [Finish] をクリックします。

Arctic Fox

Arctic Fox で、ライブラリ モジュールを作成してテスト モジュールに変換します。

新しいモジュールを追加する

プロジェクトに新しいモジュールを追加します。このモジュールで Macrobenchmark テストを保持します。

  1. Android Studio の [Project] パネルでプロジェクトまたはモジュールを右クリックして、[New] > [Module] をクリックします。
  2. [Templates] ペインで [Android Library] を選択します。
  3. モジュール名には「macrobenchmark」と入力します。
  4. [Minimum SDK] を [API 23: Android M] に設定します。
  5. [Finish] をクリックします。

新しいライブラリ モジュールを構成する

Gradle ファイルを変更する

Macrobenchmark モジュールの build.gradle を次のようにカスタマイズします。

  1. プラグインを com.android.library から com.android.test に変更します。
  2. 必要なテスト モジュール プロパティを android {} ブロックに追加します。
  3.    targetProjectPath = ":app" // Note that your module name may be different
    
       // Enable the benchmark to run separately from the app process
       experimentalProperties["android.experimental.self-instrumenting"] = true
       buildTypes {
           // Declare a build type (release) to match the target app's build type
           release {
               debuggable = true
           }
       }
  4. testImplementation または androidTestImplementation という名前の依存関係をすべて implementation に変更します。
  5. Macrobenchmark ライブラリへの依存関係を追加します。
    • implementation 'androidx.benchmark:benchmark-macro-junit4:1.1.0-alpha13'
  6. android {} ブロックの後、dependencies {} ブロックの前に、以下の行を追加します。
  7.    androidComponents {
          beforeVariants(selector().all()) {
              // Enable only the benchmark buildType, since we only want to measure
              // release-like build performance (should match app buildType)
              enabled = buildType == 'benchmark'
          }
       }

ディレクトリ構造を簡素化する

com.android.test モジュールには、すべてのテスト用のソース ディレクトリが 1 つだけあります。他のソース ディレクトリ(src/testsrc/androidTest など)は使用しないので、削除します。

参考として、サンプル Macrobenchmark モジュールをご覧ください。

マクロ ベンチマークを作成する

そのモジュールの新しいテストクラスを定義し、アプリのパッケージ名を入力します。

@RunWith(AndroidJUnit4::class)
class SampleStartupBenchmark {
    @get:Rule
    val benchmarkRule = MacrobenchmarkRule()

    @Test
    fun startup() = benchmarkRule.measureRepeated(
        packageName = "mypackage.myapp",
        metrics = listOf(StartupTimingMetric()),
        iterations = 5,
        startupMode = StartupMode.COLD
    ) { // this = MacrobenchmarkScope
        pressHome()
        val intent = Intent()
        intent.setPackage("mypackage.myapp")
        intent.setAction("mypackage.myapp.myaction")
        startActivityAndWait(intent)
    }
}
   

アプリを設定する

アプリ(マクロ ベンチマークの「ターゲット」と呼びます)のベンチマークを行うには、そのアプリをプロファイリング可能にして、詳細なトレース情報を読み取れるようにする必要があります。これは、アプリの AndroidManifest.xml<application> タグで有効にします。

<application ... >
    <!-- Profileable to enable Macrobenchmark profiling -->
    <!-- Suppress AndroidElementNotAllowed -->
    <profileable android:shell="true"/>
    ...
</application>

ベンチマーク対象のアプリは、できる限りユーザー エクスペリエンスに近づくように構成します。アプリはデバッグ不可として設定します。パフォーマンスを高めるために、圧縮をオンにすることをおすすめします。そのためには、通常、release バリアントの benchmark コピーを作成します。これは同じ処理を行いますが、debug キーを使用してローカルで署名されます。

buildTypes {
    benchmark {
        // duplicate any release build type settings for measurement accuracy,
        // such as "minifyEnabled" and "proguardFiles" in this block

        debuggable false
        signingConfig signingConfigs.debug
    }
}

Gradle 同期を実行して、左側の [Build Variants] パネルを開き、アプリと Macrobenchmark モジュールの両方の benchmark バリアントを選択します。これにより、ベンチマークを実行すると、確実にアプリの正しいバリアントをビルドしてテストできるようになります。

ベンチマーク バリアントを選択する

内部アクティビティで Macrobenchmark を実行するには、追加の手順を実施する必要があります。exported=false の内部アクティビティのベンチマークを行うには、setupBlockMacrobenchmarkRule.measureRepeated() に渡してベンチマークを行うコードに移動し、measureBlock を使用して、測定する実際のアクティビティ起動アクションまたはスクロール アクションを呼び出します。

マクロ ベンチマークをカスタマイズする

CompilationMode

マクロ ベンチマークでは、CompilationMode を指定して、アプリのどのくらいの容量を DEX バイトコード(APK 内のバイトコード形式)からマシンコード(プリコンパイル後の C++ のようなコード)にプリコンパイルするかを定義できます。

デフォルトでは、マクロ ベンチーマークは CompilationMode.DEFAULT で実行され、Android Nougat(API 24)以降ではベースライン プロファイルをインストールし(利用可能な場合)、Android Marshmallow(API 23)以前では APK を完全にコンパイルします(デフォルトのシステム動作です)。

ターゲット アプリにベースライン プロファイルと ProfileInstaller ライブラリの両方が含まれている場合は、ベースライン プロファイルをインストールできます。

Android Nougat(API 24)以降では、CompilationMode をカスタマイズしてデバイス上のプリコンパイルの容量を調整し、さまざまなレベルの事前(AOT)コンパイルや JIT キャッシュを模倣することができます。CompilationMode.FullCompilationMode.PartialCompilationMode.None をご覧ください。

この機能は、ART コンパイル コマンドをベースにして構築されています。ベンチマーク間の干渉が起こらないように、各ベンチマークは開始前にプロファイル データを消去します。

起動

アクティビティの起動を実行するには、事前定義された起動モード(COLDWARMHOT のいずれか)を measureRepeated() 関数に渡します。このパラメータにより、アクティビティの起動方法と、テスト開始時のプロセス状態が変更されます。

起動の種類について詳しくは、Android Vitals の起動に関するドキュメントをご覧ください。

スクロールとアニメーション

ほとんどの Android UI テストと異なり、Macrobenchmark テストはアプリ自体とは別個のプロセスで実行されます。これは、アプリプロセスの強制終了やシェルコマンドによるアプリのコンパイルなどを行うために必要です。

アプリを実行するには、UI Automator ライブラリか、テストプロセスからターゲット アプリを制御できるその他のメカニズムを使用します。Espresso や ActivityScenario などのアプローチは、アプリと共有するプロセスでの実行を想定しているため、機能しません。

次の例では、RecyclerView を検出するためにそのリソース ID を使用し、何度か下にスクロールしています。

@Test
fun measureScroll() {
    benchmarkRule.measureRepeated(
        packageName = "mypackage.myapp",
        metrics = listOf(FrameTimingMetric()),
        iterations = 5,
        setupBlock = {
            // before starting to measure, navigate to the UI to be measured
            val intent = Intent()
            intent.action = ACTION
            startActivityAndWait(intent)
        }
    ) {
        val recycler = device.findObject(By.res("mypackage.myapp", "recycler_id"))
        // Set gesture margin to avoid triggering gesture nav
        // with input events from automation.
        recycler.setGestureMargin(device.displayWidth / 5)

        // Scroll down several times
        for (i in 1..10) {
            recycler.scroll(Direction.DOWN, 2f)
            device.waitForIdle()
        }
    }
}

テストで FrameTimingMetric を指定すると、フレーム時間が記録され、フレーム時間の分布の概要(50 パーセンタイル、90 パーセンタイル、95 パーセンタイル、99 パーセンタイル)として報告されます。

ベンチマークで UI をスクロールする必要はありません。たとえば、代わりにアニメーションを実行できます。また、特に UI Automator を使用する必要もありません。ビューシステムによってフレーム(Compose によって生成されるフレームを含む)が生成されている間、パフォーマンス指標が収集されます。Espresso などのプロセス内メカニズムは、テストアプリ プロセスからアプリを実行する必要があるため、機能しません。

マクロ ベンチマークを実行する

Android Studio 内からテストを実行して、デバイス上のアプリのパフォーマンスを測定します。エミュレータはエンドユーザー エクスペリエンスを表すパフォーマンス値を生成しないので、エミュレータではなく実機でテストを実行する必要があります

継続的インテグレーションでベンチマークを実行およびモニタリングする方法については、CI でベンチマークを実行するセクションをご覧ください。

connectedCheck コマンドを実行して、コマンドラインからすべてのベンチマークを実行することもできます。

$ ./gradlew :macrobenchmark:connectedCheck

構成エラー

アプリが誤って(デバッグ可能またはプロファイリング不可に)構成されている場合、Macrobenchmark は不正確または不完全な測定を報告するのではなく、エラーをスローします。androidx.benchmark.suppressErrors 引数を使用すると、このようなエラーを抑制できます。

エミュレータまたは電池容量の少ないデバイスで測定しようとした場合も、コアの可用性とクロック速度が損なわれる可能性があるため、エラーがスローされます。

トレースを検査する

測定される反復処理は、それぞれが個別のシステム トレースをキャプチャします。結果トレースは、[Test Results] ペインにあるいずれかのリンクをクリックすると表示できます。このトピックの Jetpack Macrobenchmark セクションの画像をご覧ください。トレースが読み込まれると、Android Studio によって、分析するプロセスの選択を求められます。選択するターゲット アプリのプロセスは事前入力されます。

Studio トレース プロセスの選択

トレース ファイルが読み込まれると、Studio は CPU Profiler ツールで結果を表示します。

Studio トレース

手動でトレース ファイルにアクセスする

以前のバージョン(2020.3.1 より前)の Android Studio を使用している場合、または Perfetto ツールを使用してトレース ファイルを分析する場合は、追加の手順が必要です。

まず、デバイスからトレース ファイルを取得します。

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

additionalTestOutputDir 引数を使用して出力ファイルをカスタマイズしている場合は、出力パスがこれと異なる可能性があります。ログが作成された場所を確認するには、logcat でトレースパスのログを探します。次に例を示します。

I PerfettoCapture: Writing to /storage/emulated/0/Android/data/androidx.benchmark.integration.macrobenchmark.test/cache/TrivialStartupBenchmark_startup[mode=COLD]_iter002.perfetto-trace.

または、Gradle コマンドライン(./gradlew macrobenchmark:connectedCheck など)を使用してテストを呼び出す場合は、テスト結果ファイルをホストシステムのテスト出力ディレクトリにコピーできます。これを行うには、プロジェクトの gradle.properties ファイルに次の行を追加します。

android.enableAdditionalTestOutput=true

テスト実行の結果ファイルは、次のようにプロジェクトのビルド ディレクトリに表示されます。

build/outputs/connected_android_test_additional_output/debugAndroidTest/connected/<device-name>/TrivialStartupBenchmark_startup[mode=COLD]_iter002.perfetto-trace

ホストシステムにトレース ファイルが作成されたら、Android Studio の [File] > [Open] メニューからトレース ファイルを開くことができます。前のセクションで示したプロファイラ ツールのビューが表示されます。

代わりに Perfetto ツールを使用することもできます。Perfetto では、トレース中にデバイスで実行されているすべてのプロセスを検査できます。これに対して、Android Studio の CPU Profiler では、検査は単一のプロセスに限定されます。

カスタム イベントを使用してトレースデータを改善する

これは、カスタム トレース イベントを使ってアプリをインストルメント化する場合に有用です。カスタム トレース イベントは他のトレース レポートに表示され、アプリ固有の問題を特定するために役立ちます。カスタム トレース イベントの作成方法については、カスタム イベントを定義するガイドをご覧ください。

CI でベンチマークを実行する

一般的に、テストは Gradle を使用せずに CI で実行するか、別のビルドシステムを使用している場合はローカルで実行します。このセクションでは、実行時に CI を使用するように Macrobenchmark を構成する方法について説明します。

結果ファイル: JSON とトレース

MacroBenchmark は、1 つの JSON ファイルと複数のトレース ファイルを出力します。トレース ファイルは、個々の MacrobenchmarkRule.measureRepeated ループの測定される反復処理ごとに 1 つずつあります。

これらのファイルが作成される場所を定義するには、実行時に次のインストルメンテーション引数を渡します。

-e additionalTestOutputDir "device_path_you_can_write_to"

簡便化のために /sdcard/ でパスを指定できますが、Macrobenchmark モジュール内で requestLegacyExternalStoragetrue に設定することにより、対象範囲別ストレージをオプトアウトする必要があります。

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

または、インストルメンテーション引数を渡して、テストの対象範囲別ストレージをバイパスします。

-e no-isolated-storage 1

JSON の例

単一の起動ベンチマークでの JSON 出力の例を以下に示します。

{
    "context": {
        "build": {
            "device": "walleye",
            "fingerprint": "google/walleye/walleye:10/QQ3A.200805.001/6578210:userdebug/dev-keys",
            "model": "Pixel 2",
            "version": {
                "sdk": 29
            }
        },
        "cpuCoreCount": 8,
        "cpuLocked": false,
        "cpuMaxFreqHz": 2457600000,
        "memTotalBytes": 3834605568,
        "sustainedPerformanceModeEnabled": false
    },
    "benchmarks": [
        {
            "name": "startup",
            "params": {},
            "className": "androidx.benchmark.integration.macrobenchmark.SampleStartupBenchmark",
            "totalRunTimeNs": 77969052767,
            "metrics": {
                "startupMs": {
                    "minimum": 228,
                    "maximum": 283,
                    "median": 242,
                    "runs": [
                        238,
                        283,
                        256,
                        228,
                        242
                    ]
                }
            },
            "warmupIterations": 3,
            "repeatIterations": 5,
            "thermalThrottleSleepSeconds": 0
        }
    ]
}

参考情報

サンプル プロジェクトは、GitHub の Android/performance-samples リポジトリの一部として入手できます。

パフォーマンスの低下を検出する方法については、CI のベンチマークによる回帰対策をご覧ください。

フィードバック

Jetpack Macrobenchmark に関する問題の報告または機能リクエストの送信については、公開されている Issue Trackerをご利用ください。