[TOC]
The public documentation at d.android.com/benchmark explains how to use the library - this page focuses on specifics to writing libraries in the AndroidX repo, and our continuous testing / triage process.
This page is for MICRO benchmarks measuring CPU performance of small sections of code. If you're looking for measuring startup or jank, see the guide for MACRObenchmarks here.
Benchmarks are just regular instrumentation tests! Just use the
BenchmarkRule
provided by the library:
@RunWith(AndroidJUnit4::class)
class ViewBenchmark {
@get:Rule
val benchmarkRule = BenchmarkRule()
@Test
fun simpleViewInflate() {
val context = InstrumentationRegistry
.getInstrumentation().targetContext
val inflater = LayoutInflater.from(context)
val root = FrameLayout(context)
benchmarkRule.measure {
inflater.inflate(R.layout.test_simple_view, root, false)
}
}
}@RunWith(AndroidJUnit4.class)
public class ViewBenchmark {
@Rule
public BenchmarkRule mBenchmarkRule = new BenchmarkRule();
@Test
public void simpleViewInflate() {
Context context = InstrumentationRegistry
.getInstrumentation().getTargetContext();
final BenchmarkState state = mBenchmarkRule.getState();
LayoutInflater inflater = LayoutInflater.from(context);
FrameLayout root = new FrameLayout(context);
while (state.keepRunning()) {
inflater.inflate(R.layout.test_simple_view, root, false);
}
}
}As in the public documentation, benchmarks in the AndroidX repo are test-only library modules. Differences for AndroidX repo:
- Module must apply
id("androidx.benchmark")in the plugin block - Module should live in
integration-testsgroup directory to follow convention - Module name should end with
-benchmarkinsettings.gradleto follow convention - Module should not contain non-benchmark tests to avoid wasting resources in benchmark postsubmit
Applying the benchmark plugin give you benefits from the AndroidX plugin:
- Inclusion in microbenchmark CI runs
- AOT Compilation of module (local and CI) for stability
- Disable ANR avoidance in local runs (so you always get method traces)
But note that these can be detrimental for non-benchmark code.
Start by copying one of the following non-Compose projects:
Many Compose libraries already have benchmark modules:
- Compose UI Benchmarks
- Compose Runtime Benchmarks
- Compose Material Benchmarks
- Wear Compose Material Benchmarks
See the public profiling guide for more details.
Jetpack benchmark supports capturing profiling information by setting instrumentation arguments. Stack sampling and method tracing can be performed either from CLI or Studio invocation.
Args can be set in your benchmark's build.gradle, which will affect both
Studio / command-line gradlew runs. Runs from Studio will link result traces
that can be opened directly from the IDE.
android {
defaultConfig {
// must be one of: 'None', 'StackSampling', or 'MethodTracing'
testInstrumentationRunnerArgument 'androidx.benchmark.profiling.mode', 'StackSampling'
}
}
Args can also be passed from CLI. Here's an example which runs the
androidx.compose.material.benchmark.CheckboxesInRowsBenchmark#draw method with
StackSampling profiling:
./gradlew compose:material:material-benchmark:cC \
-P android.testInstrumentationRunnerArguments.androidx.benchmark.profiling.mode=StackSampling \
-P android.testInstrumentationRunnerArguments.class=androidx.compose.material.benchmark.CheckboxesInRowsBenchmark#draw
The command output will tell you where to look for the file on your host machine:
04:33:49 I/Benchmark: Benchmark report files generated at
/androidx-main/out/ui/ui/integration-tests/benchmark/build/outputs/connected_android_test_additional_output
To inspect the captured trace, open the appropriate *.trace file in that
directory with Android Studio, using File > Open.
NOTE For stack sampling, it's recommended to profile on Android Q(API 29) or higher, as this enables the benchmark library to use Simpleperf when capturing samples.
For more information on the StackSampling and MethodTracing profiling modes,
see the
Studio Profiler recording configuration docs,
specifically "Sample C/C++ Functions" (called "Callstack sample" in recent
versions), and Java Method Tracing.
Profiling for allocations requires Studio to capture, and a debuggable build. Do not commit the following changes.
First, set your benchmark to be debuggable in your benchmark module's
androidTest/AndroidManifest.xml:
<application
...
android:debuggable="false"
tools:ignore="HardcodedDebugMode"/>
Note that switching to the debug variant will likely not work, as Studio will fail to find the benchmark as a test source.
Next select ConnectedAllocation in your benchmark module's build.gradle:
android {
defaultConfig {
// --- Local only, don't commit this! ---
// pause for manual profiler connection before/after a single run of
// the benchmark loop, after warmup
testInstrumentationRunnerArgument 'androidx.benchmark.profiling.mode', 'ConnectedAllocation'
}
}
Run File > Sync Project with Gradle Files, or sync if Studio asks you. Now any
benchmark runs in that project will permit debuggable, and pause before and
after the test, to allow you to connect a profiler and start recording, and then
stop recording.
After the benchmark test starts, you have about 20 seconds to connect the profiler:
- Click the profiler tab at the bottom
- Click the plus button in the top left,
<device name>,<process name> - Click the memory section, and right click the window, and select
Record allocations. - Approximately 20 seconds later, right click again and select
Stop recording.
If timed correctly, you'll have started and stopped collection around the single run of your benchmark loop, and see all allocations in detail with call stacks in Studio.
As many Android apps don't yet enable R8, the default for microbenchmarks in AndroidX is to run with R8 disabled to measure worst-case performance. It may still be useful to run your microbenchmarks with R8 enabled locally however, and that is supported experimentally. To do this in your microbench module, set the androidTest minification property:
android {
buildTypes.release.androidTest.enableMinification = true
}
Then, if you see any errors from classes not found at runtime, you can add proguard rules here, or in a similar place for your module.
