Jetpack Benchmark ライブラリの概要

アプリコードのベンチマークを行う

Jetpack Benchmark ライブラリを使用すると、Kotlin ベースのコードでも Java ベースのコードでも Android Studio 内ですぐにベンチマークを行うことができます。ライブラリはウォームアップを処理し、コードのパフォーマンスと割り当てを測定して、ベンチマーク結果を Android Studio コンソールJSON ファイルの両方に詳細情報とともに出力します。

ユースケースとしては、RecyclerView のスクロール、データベースのクエリ、コードのうち処理速度が遅い部分(高速化する部分)の測定などがあります。

このライブラリは、継続的インテグレーション(CI)環境で使用できます。詳細については、継続的インテグレーションでベンチマークを実行するをご覧ください。

ベンチマーク対象のプロジェクトでまだ AndroidX を採用していない場合は、Android Studio を使用して既存のプロジェクトを移行するをご覧ください。

クイックスタート

このセクションでは、コードをモジュールに移動せずに簡単にベンチマークを試す手順について説明します。正確なパフォーマンス結果を得るため、この手順を行う際、デバッグが無効になります。この変更内容がソース管理システムにコミットされることはありません。これは、単発測定を行う場合に便利です。

単発ベンチマークを迅速に実行する手順は次のとおりです。

  1. モジュールの build.gradle ファイルにライブラリを追加します。

    project_root/module_dir/build.gradle

    Groovy

    buildscript {
    ...
    dependencies {
        ...
        classpath "androidx.benchmark:benchmark-gradle-plugin:1.0.0"
    }
    }
    

    Kotlin

    dependencies {
        androidTestImplementation("androidx.benchmark:benchmark-junit4:1.0.0")
    }
    

    Groovy

    dependencies {
        androidTestImplementation "androidx.benchmark:benchmark-junit4:1.0.0"
    }
    
  2. テスト マニフェストでデバッグを無効にするには、次のように <application> 要素を更新して、一時的にデバッグを強制的に無効にします。

    project_root/module_dir/src/androidTest/AndroidManifest.xml

    <!-- Important: disable debuggable for accurate performance results -->
    <application
        android:debuggable="false"
        tools:ignore="HardcodedDebugMode"
        tools:replace="android:debuggable"/>
    
  3. ベンチマークを追加するには、BenchmarkRule のインスタンスを androidTest ディレクトリ内のテストファイルに追加します。ベンチマークの作成方法については、ベンチマークを作成するをご覧ください。

    ベンチマークを JUnit テストに追加する方法を以下のコード スニペットに示します。

    Kotlin

    @RunWith(AndroidJUnit4::class)
    class MyBenchmark {
        @get:Rule
        val benchmarkRule = BenchmarkRule()
    
        @Test
        fun benchmarkSomeWork() = benchmarkRule.measureRepeated {
            doSomeWork()
        }
    }
    

    Java

    @RunWith(AndroidJUnit4.class)
    class MyBenchmark {
        @Rule
        public BenchmarkRule benchmarkRule = new BenchmarkRule();
    
        @Test
        public void benchmarkSomeWork() {
            final BenchmarkState state = benchmarkRule.getState();
            while (state.keepRunning()) {
                doSomeWork();
            }
        }
    }
    

ベンチマークを行うタイミング

ベンチマークを作成する前に、コードをプロファイリングすることをおすすめします。これにより、最適化が必要な、費用の高いオペレーションを見つけることができます。また、実行中に動作の内容を表示して、処理が遅くなる理由を明らかにすることもできます。たとえば、優先度の低いスレッドでの実行、ディスクへのアクセスによるスリープ、ビットマップのデコードなど費用の高い関数の予期せぬ呼び出しなどがあります。

TraceCompat API(または -ktx ラッパー)を介してカスタム トレース ポイントを追加すると、Android Studio CPU Profiler または Systrace でトレース ポイントを表示できます。

Kotlin

fun proccessBitmap(bitmap: Bitmap): Bitmap {
    trace("processBitmap") {
        // perform work on bitmap...
    }
}

Java

public Bitmap processBitmap(Bitmap bitmaptest) {
    TraceCompat.beginSection("processBitmap");
    try {
        // perform work on bitmap...
    } finally {
        TraceCompat.endSection();
    }
}

ベンチマークに適した対象

ベンチマークは、アプリ内で何度も実行される CPU 処理を対象とした場合に最も役に立ちます。たとえば、RecyclerView のスクロール、データ変換 / 処理、繰り返し使用されるコードブロックなどが好例です。

他のタイプのコードの場合、ベンチマークで測定するのが難しくなります。ベンチマークはループ実行されるため、頻繁に実行されないコードや、呼び出されるたびに実行方法が変化するコードの場合、ベンチマークに適していません。

キャッシュ

キャッシュのみを測定しないように注意してください。たとえば、カスタムビューのレイアウトのベンチマークを行う場合、レイアウト キャッシュのパフォーマンスのみを測定してしまう可能性があります。これを回避するには、ループごとに異なるレイアウト パラメータを渡します。ただし、ファイル システムのパフォーマンスを測定するケースなどでは、ループ中に OS がファイル システムのキャッシュを保存するため、この回避策も難しい場合があります。

頻繁に実行されないコード

アプリの起動中に 1 回実行されるコードの場合、Android ランタイム(ART)によって実行時にコンパイルされる可能性はほとんどありません。そのため、このようなコードをループ実行するベンチマークは、そのパフォーマンスを測定する方法として現実的ではありません。

このようなタイプのコードの場合、アプリ内でコードをトレースするかプロファイリングすることをおすすめします。「起動パス内のコードはベンチマークを実行できない」というわけではありませんが、実行時にコンパイルされる可能性の高いループ実行コードを選択するようにしてください。

フル プロジェクト セットアップ

単発のベンチマークではなく定期的なベンチマークをセットアップするには、各ベンチマークを個別のモジュールに分離します。これにより、debuggablefalse にセットするといった各種設定を、定期テストから分離できます。

ベンチマーク モジュールを追加する前に、ベンチマークを行うコードとリソースをライブラリ モジュールに追加します(まだない場合)。

このようにプロジェクトをセットアップする方法については、サンプルをご覧ください。

Android Studio プロパティを設定する

Android Studio 3.5 を使用している場合に、ベンチマーク モジュール ウィザードのサポートを有効にするには、Android Studio のプロパティを手動で設定する必要があります。Android Studio 3.6 以降では不要です。

ベンチマーク用の Android Studio テンプレートを有効にする手順は次のとおりです。

  1. Android Studio 3.5 で、[Help] > [Edit Custom Properties] をクリックします。

  2. 開いたファイルに、次の行を追加します。

    npw.benchmark.template.module=true

  3. ファイルを保存して閉じます。

  4. Android Studio を再起動します。

新しいモジュールを作成する

ベンチマーク モジュール テンプレートを使用すると、ベンチマークの設定を自動的に構成できます。

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

  1. プロジェクトまたはモジュールを右クリックして、[New] > [Module] を選択します。

  2. [Benchmark Module] を選択して、[Next] をクリックします。

    図 1. ベンチマーク モジュール

  3. モジュール名を入力して、言語を選択し、[Finish] をクリックします。

    ベンチマーク用に事前設定されたモジュールが作成され、ベンチマーク ディレクトリが追加されて、debuggablefalse に設定されます。

ベンチマークを作成する

ベンチマークは、標準的なインストルメンテーション テストです。ベンチマークを作成するには、ライブラリが提供する BenchmarkRule クラスを使用します。アクティビティのベンチマークを行うには、ActivityTestRule または ActivityScenarioRule を使用します。UI コードのベンチマークを行うには、@UiThreadTest を使用します。

ベンチマークのサンプルを以下のコードに示します。

Kotlin

@RunWith(AndroidJUnit4::class)
class ViewBenchmark {
    @get:Rule
    val benchmarkRule = BenchmarkRule()

    @Test
    fun simpleViewInflate() {
        val context = ApplicationProvider.getApplicationContext<Context>()
        val inflater = LayoutInflater.from(context)
        val root = FrameLayout(context)

        benchmarkRule.keepRunning {
            inflater.inflate(R.layout.test_simple_view, root, false)
        }
    }
}

Java

@RunWith(AndroidJUnit4::class)
public class ViewBenchmark {
    @Rule
    public BenchmarkRule benchmarkRule = new BenchmarkRule();

    @Test
    public void simpleViewInflate() {
        Context context = ApplicationProvider.getApplicationContext<Context>();
        final BenchmarkState state = benchmarkRule.getState();
        LayoutInflater inflater = LayoutInflater.from(context);
        FrameLayout root = new FrameLayout(context);

        while (state.keepRunning()) {
            inflater.inflate(R.layout.test_simple_view, root, false);
        }
    }
}

測定しない部分のコードのタイミングを無効にできます。以下のサンプルコードをご覧ください。

Kotlin

@Test
fun bitmapProcessing() = benchmarkRule.measureRepeated {
    val input: Bitmap = runWithTimingDisabled { constructTestBitmap() }
    processBitmap(input)
}

Java

@Test
public void bitmapProcessing() {
    final BenchmarkState state = benchmarkRule.getState();
    while (state.keepRunning()) {
        state.pauseTiming();
        Bitmap input = constructTestBitmap();
        state.resumeTiming();

        processBitmap(input);
    }
}

ベンチマークの実行方法については、ベンチマークを実行するをご覧ください。

ベンチマークを実行する

Android Studio 内で、@Test と同じようにベンチマークを実行します。Android Studio 3.4 以降の場合、コンソールに送信された出力を表示できます。

モジュール式のベンチマークを実行するには、benchmark/src/androidTest に移動して、Control+Shift+F10(Mac の場合は Command+Shift+R)を押します。図 2 に示すように、ベンチマークの結果がコンソールに表示されます。

Android Studio に表示されるベンチマーク出力

図 2. Android Studio に表示されるベンチマーク出力

コマンドラインから、通常の connectedCheck を実行します。

./gradlew benchmark:connectedCheck

データを収集する

追加の指標やデバイス情報を含むフル ベンチマーク レポートは、JSON 形式で利用できます。

androidx.benchmark プラグインにより、デフォルトで JSON 出力が有効になります。非 Gradle ビルド環境で JSON 出力を手動で有効にするには、インストルメンテーション引数の androidx.benchmark.output.enabletrue に設定します。

adb shell am instrument コマンドを使用した例を以下に示します。

adb shell am instrument -w -e "androidx.benchmark.output.enable" "true" com.android.foo/androidx.benchmark.junit4.AndroidBenchmarkRunner

デフォルトでは、JSON レポートは、デバイス ディスク内にあるテスト APK 用の外部共有ダウンロード フォルダに書き込まれます。このフォルダは通常、次の場所にあります。

/storage/emulated/0/Download/app_id-benchmarkData.json

必要に応じて、インストルメンテーション引数の additionalTestOutputDir を使用することで、ベンチマーク レポートの保存場所を設定できます。

adb shell am instrument -w -e "androidx.benchmark.output.enable" "true" -e "additionalTestOutputDir" "/path_to_a_directory_on_device_test_has_write_permissions_to/" com.android.foo/androidx.benchmark.junit4.AndroidBenchmarkRunner

JSON レポートはデバイスからホストにコピーされます。レポートは、ホストマシンの次の場所に書き込まれます。

project_root/module/build/outputs/connected_android_test_additional_output/debugAndroidTest/connected/device_id/app_id-benchmarkData.json

Android Gradle プラグイン 3.6 以降

Gradle を使用してコマンドラインからベンチマークを実行する場合、Android Gradle プラグイン 3.6 以降を使用しているプロジェクトであれば、プロジェクトの gradle.properties ファイルに次のフラグを追加できます。

android.enableAdditionalTestOutput=true

このフラグを追加すると、試験運用版の Android Gradle プラグイン機能が有効になり、API 16 以降を搭載しているデバイスから、ベンチマーク レポートをプルできます。

Android Gradle プラグイン 3.5 以前

旧バージョンの Android Gradle プラグインを使用している場合は、androidx.benchmark Gradle プラグインで、デバイスからホストへの JSON ベンチマーク レポートのコピーを処理します。

AGP 3.5 以前を使用し、API レベル 29 以降を対象とする場合は、ベンチマークの androidTest ディレクトリ内にある Android マニフェストにフラグを追加して、従来の外部ストレージの動作を有効にします。

<manifest ... >
  <!-- This attribute is "false" by default on apps targeting Android 10. -->
  <application android:requestLegacyExternalStorage="true" ... >
    ...
  </application>
</manifest>

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

クロック安定性

モバイル デバイスのクロックは、High 状態(高パフォーマンス時)から Low 状態(節電時やデバイス高温時)へと動的に変化します。このようなクロックの変化によって、ベンチマークの数値が大きく変化することがあります。この問題に対処する方法がライブラリに用意されています。

クロックをロックする(root 権限が必要)

安定したパフォーマンスを得るには、クロックをロックすることをおすすめします。これにより、デバイスが熱くなるほどクロックの High 状態が続いたり、ベンチマークが CPU をフルに活用していないときにクロックが Low 状態になったりしなくなります。この方法は、安定したパフォーマンスを確保するうえでは最善の方法ですが、adb root 権限が必要になるため、ほとんどのデバイスでサポートされていません。

クロックをロックするには、メインの build.gradle ファイル内にある最上位のプロジェクトのクラスパスに、指定のヘルパー プラグインを追加します。

Kotlin

buildscript {
    ...
    dependencies {
        ...
        classpath("androidx.benchmark:benchmark-gradle-plugin:1.0.0")
    }
}

ベンチマーク モジュールの build.gradle にプラグインを適用します。

Groovy

apply plugin: com.android.app
apply plugin: androidx.benchmark
...

Kotlin

plugins {
    id("com.android.app")
    id("androidx.benchmark")
    ...
}

これにより、ベンチマークを行う Gradle タスク(./gradlew lockClocks./gradlew unlockClocks など)がプロジェクトに追加されます。このタスクを利用することで、adb を使用してデバイス CPU のロック / ロック解除を指定できます。

adb に複数のデバイスが表示されている場合は、環境変数 ANDROID_SERIAL を使用して、Gradle タスクの動作対象となるデバイスを指定します。

ANDROID_SERIAL=device-id-from-adb-devices ./gradlew lockClocks

パフォーマンス維持モード

Window.setSustainedPerformanceMode() は、最大 CPU 周波数を抑えるようにアプリレベルで選択できるようにする機能で、一部のデバイスでサポートされています。サポートされているデバイス上で実行した場合、Benchmark ライブラリは、この API と独自のアクティビティ起動を併用することで、サーマル スロットリングを防ぎ、ベンチマーク結果を安定化します。

この機能は、Gradle プラグインが設定する testInstrumentationRunner によって、デフォルトで有効になります。カスタム ランナーを使用する場合は、AndroidBenchmarkRunner をサブクラス化して、testInstrumentationRunner として使用できます。

ランナーは、非透過的なフルスクリーン アクティビティを起動して、ベンチマークがフォアグラウンドで実行される状態にし、他のアプリは描画しません。

自動実行の一時停止

クロックロックとパフォーマンス維持モードのいずれも使用しない場合、ライブラリは、自動サーマル スロットリング検出を行います。有効にすると、内部ベンチマークが定期的に実行され、CPU のパフォーマンスを低下させるほどデバイスの温度が高くなっていないか判定します。CPU パフォーマンスの低下が検出されると、ライブラリは実行を一時停止し、デバイスが冷却した後で、現在のベンチマークを再試行します。

構成エラー

ライブラリは、リリース精度のパフォーマンスを実現するようにプロジェクトと環境をセットアップする際、以下の条件を検出します。

  • Debuggablefalse に設定されている。
  • エミュレータではなく物理デバイスが使用されている。
  • ルート権限取得済みのデバイスの場合、クロックがロックされている。
  • デバイスのバッテリー残量が十分にある。

上記のチェックのうち、1 つでも失敗した場合、ベンチマークはエラーをスローして、不正確な測定を行わないようにします。

このエラーを警告として抑制し、エラーのスローによってベンチマークが停止しないようにするには、抑制するエラーのタイプをカンマ区切りリストで指定し、インストルメンテーション引数 androidx.benchmark.suppressErrors に渡します。

adb shell am instrument -w -e "androidx.benchmark.suppressErrors" "DEBUGGABLE" com.android.foo/androidx.benchmark.junit4.AndroidBenchmarkRunner

Gradle 内で次のように設定できます。

Groovy

android {
    defaultConfig {
        testInstrumentationRunnerArgument 'androidx.benchmark.suppressErrors', 'DEBUGGABLE'
    }
}

Kotlin

android {
    defaultConfig {
        testInstrumentationRunnerArguments(mapOf(
            "androidx.benchmark.suppressErrors" to "DEBUGGABLE"
        ))
    }
}

エラーを抑制すると、不適切に設定された状態でもベンチマークを実行できます。ただし、そのベンチマーク出力は意図的に加工され、テスト名の前にエラー名が付加されます。たとえば、上記の抑制を使用してデバッグ可能なベンチマークを実行すると、テスト名の前に DEBUGGABLE_ が付加されます。

プロファイリング

ベンチマークをプロファイリングして、測定したコードの実行速度が遅い理由を調査できます。

ベンチマーク モジュールの build.gradle ファイルに以下の行を追加します。

Groovy

android {
    defaultConfig {
        // must be one of: none, sampled, or method
        testInstrumentationRunnerArgument 'androidx.benchmark.profiling.mode', 'sampled'
    }
}

Kotlin

android {
    defaultConfig {
        // must be one of: none, sampled, or method
        testInstrumentationRunnerArgument = mapOf(
            "androidx.benchmark.profiling.mode", "sampled"
        )
    }
}

ベンチマークを実行すると、JSON の結果と同じディレクトリで、出力された .trace ファイルがホストにコピーされます。Android Studio で [File] > [Open] を使用してこのファイルを開き、CPU Profiler のプロファイリング結果を検査します。

メソッド トレース

メソッド トレースを使用すると、メソッド トレースをキャプチャする前にベンチマークがウォームアップされ、ベンチマークによって呼び出されるすべてのメソッドが記録されます。パフォーマンスの結果は、各メソッドの開始や終了をキャプチャするオーバーヘッドの影響を大きく受けます。

メソッド プロファイリング

メソッド サンプリングでは、ウォームアップが完了した後、ベンチマーク トレースが 100 マイクロ秒間隔でサンプリングされます。有意な結果を得るために十分なサンプルがキャプチャされるように、ベンチマークは通常よりかなり長いループとなり、測定結果は多少の影響を受けます。

メソッド トレースとサンプリングの使用方法について詳しくは、CPU プロファイリングの構成をご覧ください。

ベンチマークのサンプル

次のプロジェクトで、サンプル ベンチマーク コードを利用できます。

サンプル プロジェクトには、以下が含まれています。

  • BenchmarkSample: スタンドアロンのサンプルで、ベンチマーク モジュールを使用してコードと UI を測定する方法について示します。

  • PagingWithNetworkSample: Android アーキテクチャ コンポーネントのサンプルで、RecyclerView パフォーマンスのベンチマークを行う方法について示します。

  • WorkManagerSample: Android アーキテクチャ コンポーネントのサンプルで、WorkManager ワーカーのベンチマークを行う方法について示します。

参考情報

ベンチマークについて詳しくは、以下の参考情報をご確認ください。

ブログ

フィードバックを送信する

ベンチマークの使用に関して問題の報告や機能リクエストの送信を行う場合は、公開されている Issue Tracker をご利用ください。