Jetpack Benchmarkを使ってView#measureにかかる時間を計測する
この記事は、 Android Advent Calendar 2019 - Qiita の23日目です。
はじめに
この記事では、ベンチマークを行うためのJetpack Benchmarkライブラリの紹介と、ベンチマークの例として View#measure
にかかる時間の計測を行います。
Jetpack Benchmarkとは
ベンチマークを行うためのライブラリで、Instrumented Tests( androidTest
ディレクトリ内のテスト)を書くかのようにベンチマークを行うことができます。
Benchmark app code | Android Developers
Jetpack Benchmarkの設定
ベンチマーク用のモジュールを追加
ベンチマーク用のコードを追加するためのモジュールを作成します。
Jetpack Benchmarkを使う際には debuggable=false
を指定する必要があるため、モジュールを分けておくと継続的に実行する際に便利です。
プラグインとライブラリを追加
ルートの build.gradle
にclasspathを設定した後、モジュールの build.gradle
でプラグインとライブラリを追加します。
classpath "androidx.benchmark:benchmark-gradle-plugin:1.0.0"
apply plugin: 'androidx.benchmark'
androidTestImplementation "androidx.benchmark:benchmark-junit4:1.0.0"
また、モジュール作成時に記述されている defaultConfig
内の testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
を削除しておきます。
debuggableをfalseに設定する
AndroidTest
を実行する場合のみ debuggable=false
にするためには module/src/androidTest/AndroidManifest.xml
を作成後、以下のコードを追加します。
<application android:debuggable="false" tools:ignore="HardcodedDebugMode" tools:replace="android:debuggable"/>
以上で設定は終わりとなります。ここからは実際にベンチマークを取るためのコードを書いていきます。
サンプルのベンチマークを実行
androidTest/java/package
ディレクトリに以下のクラスを作成します。
@RunWith(AndroidJUnit4::class) class ViewMeasureTest { @get:Rule val benchmarkRule = BenchmarkRule() @Test fun benchmarkSomeWork() = benchmarkRule.measureRepeated { Thread.sleep(100) } }
注目部分は以下の2点です。
- BenchmarkRule の設定
- テストコードで
benchmarkRule.measureRepeated
を使用
measureRepeated
内に実行する処理が、ベンチマークの対象となります。
ベンチマークテストは通常のテストと同様に実行するだけで、ベンチマークをとることができます。今回は結果が分かりやすいように100 msのスリープ処理を実行するようにしました。
実行してみると以下のような出力が行われます。
benchmark: 100,160,468 ns ViewMeasureTest.benchmarkSomeWork
処理に 100,160,468 ns ≒ 100 ms
かかったという出力が得られました。
このようにJetpack Benchmarkでは、テストコードと同じ記述方法でベンチマークをとることができます。
View#measureにかかる時間を計測する
もう1つの例として、weight
を使った LinearLayout
を入れ子にした場合としなかった場合の View#measure
の計測を行います。
(weight
を入れ子で使用するとAndroidStudioでは Nested weights are bad for performance
というinspectionが表示されます。)
レイアウトファイル
使用したレイアウトファイルは以下の2つです
- 入れ子にした場合 https://github.com/sckm/MeasureViewSizeBenchmark/blob/master/view-measure/src/main/res/layout/layout_nested.xml
- 入れ子にしない場合 https://github.com/sckm/MeasureViewSizeBenchmark/blob/master/view-measure/src/main/res/layout/layout_no_nested.xml
レイアウトプレビューは以下の画像のようになります。
ベンチマーク用コード
以下のコードを実行して計測を行いました。
ベンチマークテストでは measureRepeated
内で実行する処理の計測を行いますが、 runWithTimingDisabled
内に書いた処理は計測から除外することが可能です。
@Test fun noNestedWeights() { benchmarkRule.measureRepeated { val container: ViewGroup = runWithTimingDisabled { val context = ApplicationProvider.getApplicationContext<Context>() LayoutInflater.from(context).inflate(R.layout.layout_no_nested, null) as ViewGroup } measureAndLayoutWrapLength(container) } } private fun measureAndLayoutWrapLength(container: ViewGroup) { val widthMeasureSpec = MeasureSpec.makeMeasureSpec(360, MeasureSpec.EXACTLY) val heightMeasureSpec = MeasureSpec.makeMeasureSpec(640, MeasureSpec.AT_MOST) container.measure(widthMeasureSpec, heightMeasureSpec) container.layout( 0, 0, container.measuredWidth, container.measuredHeight ) }
計測結果
ベンチマークテストを実行した結果は以下のようになりました。
レイアウト構造が少し異なるため一概には言えませんが、 weight
を入れ子で使用した場合は、しなかった場合と比べて処理に大きく時間がかかっていることが分かります。
入れ子にした場合 : benchmark: 475,000 ns ViewMeasureTest.nestedWeights 入れ子にしない場合: benchmark: 233,802 ns ViewMeasureTest.noNestedWeights
最後に
Jetpack Benchmarkライブラリを使用することで、簡単に安定したベンチマークをとることができます。
今回は View#measure
を例にしましたが、公式のサンプルにあるオートボクシングのようにAndroidに依存していないコードのベンチマーク計測も可能です。
公式のサンプルはこちら performance-samples/BenchmarkSample at master · android/performance-samples · GitHub
例で使用したコードなどは以下のレポジトリで確認することができます。 github.com