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

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

ユースケースとしては、RecyclerView のスクロール、非自明な View 階層のインフレート、データベース クエリの実行などがあります。

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

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

クイックスタート

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

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

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

    project_root/module_dir/build.gradle

        dependencies {
            androidTestImplementation "androidx.benchmark:benchmark-junit4:1.0.0-beta01"
        }
        
  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 myBenchmark() {
                final BenchmarkState state = benchmarkRule.getState();
                while (state.keepRunning()) {
                    doSomeWork();
                }
            }
        }
        

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

ベンチマークは、アプリ内で何度も実行される 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()
            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();
            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 Plugin 3.6 以降

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

    android.enableAdditionalTestOutput=true
    

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

Android Gradle Plugin 3.5 以前

旧バージョンの Android Gradle Plugin を使用している場合は、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 状態(節電時やデバイス高温時)へと動的に変化します。このようなクロックの変化によって、ベンチマークの数値が大きく変化することがあります。この問題に対処する方法がライブラリに用意されています。

クロックをロックする(ルート権限が必要)

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

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

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

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

apply plugin: com.android.app
    apply plugin: 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 パフォーマンスの低下が検出されると、ライブラリは実行を一時停止し、デバイスが冷却した後で、現在のベンチマークを再試行します。

設定エラー

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

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

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

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

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

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

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

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

ベンチマークのサンプル

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

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

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

  • PagingWithNetworkSample: Android Architecture Components のサンプルで、RecyclerView パフォーマンスのベンチマークを行う方法について示します。

  • WorkManagerSample: Android Architecture Components のサンプルで、WorkManager ワーカーのベンチマークを行う方法について示します。

フィードバックを提供する

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