AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame^] | 1 | # Benchmarking in AndroidX |
| 2 | |
| 3 | [TOC] |
| 4 | |
| 5 | The public documentation at |
| 6 | [d.android.com/benchmark](http://d.android.com/benchmark) explains how to use |
| 7 | the library - this page focuses on specifics to writing libraries in the |
| 8 | AndroidX repo, and our continuous testing / triage process. |
| 9 | |
| 10 | ### Writing the benchmark |
| 11 | |
| 12 | Benchmarks are just regular instrumentation tests! Just use the |
| 13 | [`BenchmarkRule`](https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/benchmark/junit4/src/main/java/androidx/benchmark/junit4/BenchmarkRule.kt) |
| 14 | provided by the library: |
| 15 | |
| 16 | <section class="tabs"> |
| 17 | |
| 18 | #### Kotlin {.new-tab} |
| 19 | |
| 20 | ```kotlin |
| 21 | @RunWith(AndroidJUnit4::class) |
| 22 | class ViewBenchmark { |
| 23 | @get:Rule |
| 24 | val benchmarkRule = BenchmarkRule() |
| 25 | |
| 26 | @Test |
| 27 | fun simpleViewInflate() { |
| 28 | val context = InstrumentationRegistry |
| 29 | .getInstrumentation().targetContext |
| 30 | val inflater = LayoutInflater.from(context) |
| 31 | val root = FrameLayout(context) |
| 32 | |
| 33 | benchmarkRule.measure { |
| 34 | inflater.inflate(R.layout.test_simple_view, root, false) |
| 35 | } |
| 36 | } |
| 37 | } |
| 38 | ``` |
| 39 | |
| 40 | #### Java {.new-tab} |
| 41 | |
| 42 | ```java |
| 43 | @RunWith(AndroidJUnit4.class) |
| 44 | public class ViewBenchmark { |
| 45 | @Rule |
| 46 | public BenchmarkRule mBenchmarkRule = new BenchmarkRule(); |
| 47 | |
| 48 | @Test |
| 49 | public void simpleViewInflate() { |
| 50 | Context context = InstrumentationRegistry |
| 51 | .getInstrumentation().getTargetContext(); |
| 52 | final BenchmarkState state = mBenchmarkRule.getState(); |
| 53 | LayoutInflater inflater = LayoutInflater.from(context); |
| 54 | FrameLayout root = new FrameLayout(context); |
| 55 | |
| 56 | while (state.keepRunning()) { |
| 57 | inflater.inflate(R.layout.test_simple_view, root, false); |
| 58 | } |
| 59 | } |
| 60 | } |
| 61 | ``` |
| 62 | |
| 63 | </section> |
| 64 | |
| 65 | ## Project structure |
| 66 | |
| 67 | As in the public documentation, benchmarks in the AndroidX repo are test-only |
| 68 | library modules. Differences for AndroidX repo: |
| 69 | |
| 70 | 1. Module name must end with `-benchmark` in `settings.gradle`. |
| 71 | 2. You do not need to apply the benchmark plugin (it's pulled in automatically |
| 72 | from source) |
| 73 | |
| 74 | ### I'm lazy and want to start quickly |
| 75 | |
| 76 | Start by copying one of the following projects: |
| 77 | |
| 78 | * [navigation-benchmark](https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/navigation/benchmark/) |
| 79 | * [recyclerview-benchmark](https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/recyclerview/recyclerview-benchmark/) |
| 80 | |
| 81 | ### Compose |
| 82 | |
| 83 | Compose builds the benchmark from source, so usage matches the rest of the |
| 84 | AndroidX project. See existing Compose benchmark projects: |
| 85 | |
| 86 | * [Compose UI benchmarks](https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/ui/integration-tests/benchmark/) |
| 87 | * [Compose Runtime benchmarks](https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/compose/compose-runtime/compose-runtime-benchmark/) |
| 88 | |
| 89 | ## Profiling |
| 90 | |
| 91 | ### Command Line |
| 92 | |
| 93 | The benchmark library supports capturing profiling information - sampled and |
| 94 | method - from the command line. Here's an example which runs the |
| 95 | `androidx.ui.benchmark.test.CheckboxesInRowsBenchmark#draw` method with |
| 96 | `MethodSampling` profiling: |
| 97 | |
| 98 | ``` |
| 99 | ./gradlew compose:integ:bench:cC \ |
| 100 | -P android.testInstrumentationRunnerArguments.androidx.benchmark.profiling.mode=MethodSampling \ |
| 101 | -P android.testInstrumentationRunnerArguments.class=androidx.ui.benchmark.test.CheckboxesInRowsBenchmark#draw |
| 102 | ``` |
| 103 | |
| 104 | The command output will tell you where to look for the file on your host |
| 105 | machine: |
| 106 | |
| 107 | ``` |
| 108 | 04:33:49 I/Benchmark: Benchmark report files generated at |
| 109 | /androidx-master-dev/out/ui/ui/integration-tests/benchmark/build/outputs/connected_android_test_additional_output |
| 110 | ``` |
| 111 | |
| 112 | To inspect the captured trace, open the appropriate `*.trace` file in that |
| 113 | directory with Android Studio, using `File > Open`. |
| 114 | |
| 115 | For more information on the `MethodSampling` and `MethodTracing` profiling |
| 116 | modes, see the |
| 117 | [Studio Profiler configuration docs](https://developer.android.com/studio/profile/cpu-profiler#configurations), |
| 118 | specifically Java Sampled Profiling, and Java Method Tracing. |
| 119 | |
| 120 |  |
| 121 | |
| 122 | ### Advanced: Simpleperf Method Sampling |
| 123 | |
| 124 | [Simpleperf](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/) |
| 125 | offers more accurate profiling for apps than standard method sampling, due to |
| 126 | lower overhead (as well as C++ profiling support). Simpleperf support will be |
| 127 | simplified and improved over time. |
| 128 | |
| 129 | [Simpleperf app profiling docs](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/android_application_profiling.md). |
| 130 | |
| 131 | #### Device |
| 132 | |
| 133 | Get an API 28+ device (Or a rooted API 27 device). The rest of this section is |
| 134 | about *why* those constraints exist, skip if not interested. |
| 135 | |
| 136 | Simpleperf has restrictions about where it can be used - Jetpack Benchmark will |
| 137 | only support API 28+ for now, due to |
| 138 | [platform/simpleperf constraints](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/android_application_profiling.md#prepare-an-android-application) |
| 139 | (see last subsection titled "If you want to profile Java code"). Summary is: |
| 140 | |
| 141 | - <=23 (M): Unsupported for Java code. |
| 142 | |
| 143 | - 24-25 (N): Requires compiled Java code. We haven't investigated support. |
| 144 | |
| 145 | - 26 (O): Requires compiled Java code, and wrapper script. We haven't |
| 146 | investigated support. |
| 147 | |
| 148 | - 27 (P): Can profile all Java code, but requires `userdebug`/rooted device |
| 149 | |
| 150 | - \>=28 (Q): Can profile all Java code, requires profileable (or |
| 151 | `userdebug`/rooted device) |
| 152 | |
| 153 | We aren't planning to support profiling debuggable APK builds, since they're |
| 154 | misleading for profiling. |
| 155 | |
| 156 | #### Initial setup |
| 157 | |
| 158 | Currently, we rely on Python scripts built by the simpleperf team. We can |
| 159 | eventually build this into the benchmark library / gradle plugin. Download the |
| 160 | scripts from AOSP: |
| 161 | |
| 162 | ``` |
| 163 | # copying to somewhere outside of the androidx repo |
| 164 | git clone https://android.googlesource.com/platform/system/extras ~/simpleperf |
| 165 | ``` |
| 166 | |
| 167 | Next configure your path to ensure the ADB that the scripts will use matches the |
| 168 | androidx tools: |
| 169 | |
| 170 | ``` |
| 171 | export PATH=$PATH:<path/to/androidx>/prebuilts/fullsdk-<linux or darwin>/platform-tools |
| 172 | ``` |
| 173 | |
| 174 | Now, setup your device for simpleperf: |
| 175 | |
| 176 | ``` |
| 177 | ~/simpleperf/simpleperf/scripts/api_profiler.py prepare --max-sample-rate 10000000 |
| 178 | ``` |
| 179 | |
| 180 | #### Build and Run, Option 1: Studio (slightly recommended) |
| 181 | |
| 182 | Running from Studio is simpler, since you don't have to manually install and run |
| 183 | the APKs, avoiding Gradle. |
| 184 | |
| 185 | Add the following to the benchmark module's build.gradle: |
| 186 | |
| 187 | ``` |
| 188 | android { |
| 189 | defaultConfig { |
| 190 | // DO NOT COMMIT!! |
| 191 | testInstrumentationRunnerArgument 'androidx.benchmark.profiling.mode', 'MethodSamplingSimpleperf' |
| 192 | // Optional: Control freq / duration. |
| 193 | testInstrumentationRunnerArgument 'androidx.benchmark.profiler.sampleFrequency', '1000000' |
| 194 | testInstrumentationRunnerArgument 'androidx.benchmark.profiler.sampleDurationSeconds', '5' |
| 195 | } |
| 196 | } |
| 197 | ``` |
| 198 | |
| 199 | And run the test or tests you'd like to measure from within Studio. |
| 200 | |
| 201 | #### Build and Run, Option 2: Command Line |
| 202 | |
| 203 | **Note - this will be significantly simplified in the future** |
| 204 | |
| 205 | Since we're not using AGP to pull the files yet, we can't invoke the benchmark |
| 206 | through Gradle, because Gradle uninstalls after each test run. Instead, let's |
| 207 | just build and run manually: |
| 208 | |
| 209 | ``` |
| 210 | ./gradlew compose:integration-tests:benchmark:assembleReleaseAndroidTest |
| 211 | |
| 212 | adb install -r ../../../out/ui/compose/integration-tests/benchmark/build/outputs/apk/androidTest/release/benchmark-release-androidTest.apk |
| 213 | |
| 214 | # run the test (can copy this line from Studio console, when running a benchmark) |
| 215 | adb shell am instrument -w -m --no-window-animation -e androidx.benchmark.profiling.mode MethodSamplingSimpleperf -e debug false -e class 'androidx.ui.benchmark.test.CheckboxesInRowsBenchmark#toggleCheckbox_draw' androidx.ui.benchmark.test/androidx.benchmark.junit4.AndroidBenchmarkRunner |
| 216 | ``` |
| 217 | |
| 218 | #### Pull and open the trace |
| 219 | |
| 220 | ``` |
| 221 | # move the files to host |
| 222 | # (Note: removes files from device) |
| 223 | ~/simpleperf/simpleperf/scripts/api_profiler.py collect -p androidx.ui.benchmark.test -o ~/simpleperf/results |
| 224 | |
| 225 | # create/open the HTML report |
| 226 | ~/simpleperf/simpleperf/scripts/report_html.py -i ~/simpleperf/results/CheckboxesInRowsBenchmark_toggleCheckbox_draw\[1\].data |
| 227 | ``` |
| 228 | |
| 229 | ### Advanced: Studio Profiling |
| 230 | |
| 231 | Profiling for allocations and simpleperf profiling requires Studio to capture. |
| 232 | |
| 233 | Studio profiling tools require `debuggable=true`. First, temporarily override it |
| 234 | in your benchmark's `androidTest/AndroidManifest.xml`. |
| 235 | |
| 236 | Next choose which profiling you want to do: Allocation, or Sampled (SimplePerf) |
| 237 | |
| 238 | `ConnectedAllocation` will help you measure the allocations in a single run of a |
| 239 | benchmark loop, after warmup. |
| 240 | |
| 241 | `ConnectedSampled` will help you capture sampled profiling, but with the more |
| 242 | detailed / accurate Simpleperf sampling. |
| 243 | |
| 244 | Set the profiling type in your benchmark module's `build.gradle`: |
| 245 | |
| 246 | ``` |
| 247 | android { |
| 248 | defaultConfig { |
| 249 | // Local only, don't commit this! |
| 250 | testInstrumentationRunnerArgument 'androidx.benchmark.profiling.mode', 'ConnectedAllocation' |
| 251 | } |
| 252 | } |
| 253 | ``` |
| 254 | |
| 255 | Run `File > Sync Project with Gradle Files`, or sync if Studio asks you. Now any |
| 256 | benchmark runs in that project will permit debuggable, and pause before and |
| 257 | after the test, to allow you to connect a profiler and start recording, and then |
| 258 | stop recording. |
| 259 | |
| 260 | #### Running and Profiling |
| 261 | |
| 262 | After the benchmark test starts, you have about 20 seconds to connect the |
| 263 | profiler: |
| 264 | |
| 265 | 1. Click the profiler tab at the bottom |
| 266 | 1. Click the plus button in the top left, `<device name>`, `<process name>` |
| 267 | 1. Next step depends on which you intend to capture |
| 268 | |
| 269 | #### Allocations |
| 270 | |
| 271 | Click the memory section, and right click the window, and select `Record |
| 272 | allocations`. Approximately 20 seconds later, right click again and select `Stop |
| 273 | recording`. |