blob: 07a68e6c967de1b82f97c03b75142c2cedf9b9b4 [file] [log] [blame] [view]
AndroidX Core Team2e416b22020-12-03 22:58:07 +00001# Benchmarking in AndroidX
2
3[TOC]
4
5The public documentation at
6[d.android.com/benchmark](http://d.android.com/benchmark) explains how to use
7the library - this page focuses on specifics to writing libraries in the
8AndroidX repo, and our continuous testing / triage process.
9
AndroidX Core Team0e7745f2021-04-08 17:00:10 +000010This page is for MICRO benchmarks measuring CPU performance of small sections of
11code. If you're looking for measuring startup or jank, see the guide for
Ian Baker186108e2023-11-20 06:54:36 -080012MACRObenchmarks [here](/docs/macrobenchmarking.md).
AndroidX Core Team0e7745f2021-04-08 17:00:10 +000013
AndroidX Core Team2e416b22020-12-03 22:58:07 +000014### Writing the benchmark
15
16Benchmarks are just regular instrumentation tests! Just use the
AndroidX Core Team5330eef2023-02-21 16:07:59 -050017[`BenchmarkRule`](https://developer.android.com/reference/kotlin/androidx/benchmark/junit4/BenchmarkRule)
AndroidX Core Team2e416b22020-12-03 22:58:07 +000018provided by the library:
19
20<section class="tabs">
21
22#### Kotlin {.new-tab}
23
24```kotlin
25@RunWith(AndroidJUnit4::class)
26class ViewBenchmark {
27 @get:Rule
28 val benchmarkRule = BenchmarkRule()
29
30 @Test
31 fun simpleViewInflate() {
32 val context = InstrumentationRegistry
33 .getInstrumentation().targetContext
34 val inflater = LayoutInflater.from(context)
35 val root = FrameLayout(context)
36
37 benchmarkRule.measure {
38 inflater.inflate(R.layout.test_simple_view, root, false)
39 }
40 }
41}
42```
43
44#### Java {.new-tab}
45
46```java
47@RunWith(AndroidJUnit4.class)
48public class ViewBenchmark {
49 @Rule
50 public BenchmarkRule mBenchmarkRule = new BenchmarkRule();
51
52 @Test
53 public void simpleViewInflate() {
54 Context context = InstrumentationRegistry
55 .getInstrumentation().getTargetContext();
56 final BenchmarkState state = mBenchmarkRule.getState();
57 LayoutInflater inflater = LayoutInflater.from(context);
58 FrameLayout root = new FrameLayout(context);
59
60 while (state.keepRunning()) {
61 inflater.inflate(R.layout.test_simple_view, root, false);
62 }
63 }
64}
65```
66
67</section>
68
69## Project structure
70
71As in the public documentation, benchmarks in the AndroidX repo are test-only
72library modules. Differences for AndroidX repo:
73
AndroidX Core Team0e7745f2021-04-08 17:00:10 +0000741. Module must live in `integration-tests` group directory
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000751. Module name must end with `-benchmark` in `settings.gradle`.
AndroidX Core Team2e416b22020-12-03 22:58:07 +000076
77### I'm lazy and want to start quickly
78
AndroidX Core Teamd9b6ba12022-02-08 10:58:40 -080079Start by copying one of the following non-Compose projects:
AndroidX Core Team2e416b22020-12-03 22:58:07 +000080
AndroidX Core Team5330eef2023-02-21 16:07:59 -050081* [navigation-benchmark](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:navigation/navigation-benchmark/)
82* [recyclerview-benchmark](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:recyclerview/recyclerview-benchmark/)
AndroidX Core Team2e416b22020-12-03 22:58:07 +000083
AndroidX Core Teamd9b6ba12022-02-08 10:58:40 -080084Many Compose libraries already have benchmark modules:
AndroidX Core Team2e416b22020-12-03 22:58:07 +000085
AndroidX Core Teamd9b6ba12022-02-08 10:58:40 -080086* [Compose UI Benchmarks](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/benchmark/)
87* [Compose Runtime Benchmarks](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/runtime/runtime/compose-runtime-benchmark/)
88* [Compose Material Benchmarks](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/benchmark/)
89* [Wear Compose Material Benchmarks](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:wear/compose/compose-material/benchmark/)
AndroidX Core Team2e416b22020-12-03 22:58:07 +000090
91## Profiling
92
AndroidX Core Teamd9b6ba12022-02-08 10:58:40 -080093See the
94[public profiling guide](https://developer.android.com/studio/profile/benchmark#profiling)
95for more details.
AndroidX Core Team2e416b22020-12-03 22:58:07 +000096
AndroidX Core Teamd9b6ba12022-02-08 10:58:40 -080097Jetpack benchmark supports capturing profiling information by setting
98instrumentation arguments. Stack sampling and method tracing can be performed
99either from CLI or Studio invocation.
100
101### Set Arguments in Gradle
102
103Args can be set in your benchmark's `build.gradle`, which will affect both
104Studio / command-line gradlew runs. Runs from Studio will link result traces
105that can be opened directly from the IDE.
106
107```
108android {
109 defaultConfig {
110 // must be one of: 'None', 'StackSampling', or 'MethodTracing'
111 testInstrumentationRunnerArgument 'androidx.benchmark.profiling.mode', 'StackSampling'
112 }
113}
114```
115
116### Set Arguments on Command Line
117
118Args can also be passed from CLI. Here's an example which runs the
AndroidX Core Team5f312b62021-08-05 15:59:15 -0700119`androidx.compose.material.benchmark.CheckboxesInRowsBenchmark#draw` method with
120`StackSampling` profiling:
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000121
122```
AndroidX Core Team5f312b62021-08-05 15:59:15 -0700123./gradlew compose:material:material-benchmark:cC \
124 -P android.testInstrumentationRunnerArguments.androidx.benchmark.profiling.mode=StackSampling \
125 -P android.testInstrumentationRunnerArguments.class=androidx.compose.material.benchmark.CheckboxesInRowsBenchmark#draw
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000126```
127
128The command output will tell you where to look for the file on your host
129machine:
130
131```
13204:33:49 I/Benchmark: Benchmark report files generated at
AndroidX Core Team408c27b2020-12-15 15:57:00 +0000133/androidx-main/out/ui/ui/integration-tests/benchmark/build/outputs/connected_android_test_additional_output
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000134```
135
136To inspect the captured trace, open the appropriate `*.trace` file in that
137directory with Android Studio, using `File > Open`.
138
AndroidX Core Team5f312b62021-08-05 15:59:15 -0700139NOTE For stack sampling, it's recommended to profile on Android Q(API 29) or
140higher, as this enables the benchmark library to use
AndroidX Core Team5330eef2023-02-21 16:07:59 -0500141[Simpleperf](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/)
142when capturing samples.
AndroidX Core Team5f312b62021-08-05 15:59:15 -0700143
144For more information on the `StackSampling` and `MethodTracing` profiling modes,
145see the
AndroidX Core Team5330eef2023-02-21 16:07:59 -0500146[Studio Profiler recording configuration docs](https://developer.android.com/studio/profile/record-traces#configurations),
AndroidX Core Team2eeefeb2023-10-02 23:16:12 -0700147specifically "Sample C/C++ Functions" (called "Callstack sample" in recent
148versions), and Java Method Tracing.
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000149
150![Sample flame chart](benchmarking_images/profiling_flame_chart.png "Sample flame chart")
151
AndroidX Core Teamd9b6ba12022-02-08 10:58:40 -0800152### Advanced: Connected Studio Profiler
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000153
AndroidX Core Team2eeefeb2023-10-02 23:16:12 -0700154Profiling for allocations requires Studio to capture, and a debuggable build. Do
155not commit the following changes.
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000156
AndroidX Core Team2eeefeb2023-10-02 23:16:12 -0700157First, set your benchmark to be debuggable in your benchmark module's
158`androidTest/AndroidManifest.xml`:
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000159
AndroidX Core Team2eeefeb2023-10-02 23:16:12 -0700160```
161 <application
162 ...
163 android:debuggable="false"
164 tools:ignore="HardcodedDebugMode"/>
165```
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000166
AndroidX Core Team2eeefeb2023-10-02 23:16:12 -0700167Note that switching to the debug variant will likely not work, as Studio will
168fail to find the benchmark as a test source.
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000169
AndroidX Core Team2eeefeb2023-10-02 23:16:12 -0700170Next select `ConnectedAllocation` in your benchmark module's `build.gradle`:
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000171
172```
173android {
174 defaultConfig {
AndroidX Core Team2eeefeb2023-10-02 23:16:12 -0700175 // --- Local only, don't commit this! ---
176 // pause for manual profiler connection before/after a single run of
177 // the benchmark loop, after warmup
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000178 testInstrumentationRunnerArgument 'androidx.benchmark.profiling.mode', 'ConnectedAllocation'
179 }
180}
181```
182
183Run `File > Sync Project with Gradle Files`, or sync if Studio asks you. Now any
184benchmark runs in that project will permit debuggable, and pause before and
185after the test, to allow you to connect a profiler and start recording, and then
186stop recording.
187
188#### Running and Profiling
189
190After the benchmark test starts, you have about 20 seconds to connect the
191profiler:
192
1931. Click the profiler tab at the bottom
1941. Click the plus button in the top left, `<device name>`, `<process name>`
AndroidX Core Team2eeefeb2023-10-02 23:16:12 -07001951. Click the memory section, and right click the window, and select `Record
196 allocations`.
1971. Approximately 20 seconds later, right click again and select `Stop
198 recording`.
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000199
AndroidX Core Team2eeefeb2023-10-02 23:16:12 -0700200If timed correctly, you'll have started and stopped collection around the single
201run of your benchmark loop, and see all allocations in detail with call stacks
202in Studio.
AndroidX Core Team92abb682024-03-11 13:57:44 -0700203
204## Minification / R8
205
206As many Android apps don't yet enable R8, the default for microbenchmarks in
207AndroidX is to run with R8 disabled to measure worst-case performance. It may
208still be useful to run your microbenchmarks with R8 enabled locally however, and
209that is supported experimentally. To enable, in your microbench module:
210
211```
212android {
213 buildTypes.release.androidTest.enableMinification = true
214}
215```
216
217Then, if you see any errors from classes not found at runtime, you can add
218proguard rules
219[here](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/benchmark-utils/proguard-rules.pro),
220or in a similar place for your module.