Why Baseline Profiles?
The Startup Problem
When a user installs your app, Android does not have profile data about which code paths are hot. ART (Android Runtime) must interpret code or compile it without optimization, leading to slow startup and jank during the first use.
Baseline profiles solve this by providing ART with a pre-built profile of critical code paths. When the app is installed, ART uses this profile to ahead-of-time (AOT) compile the most important methods, resulting in faster startup and smoother initial interactions.
How ART Compilation Works
Android uses a hybrid compilation strategy:
- Interpretation: Code is interpreted initially for fast installation
- JIT (Just-In-Time): Frequently executed methods are compiled at runtime
- AOT (Ahead-Of-Time): At install or during idle charging, JIT profiles are used to AOT compile hot methods
- Baseline Profile: Provides a pre-built profile to AOT compile critical code before the user even opens the app
Without baseline profile:
First launch → Interpret → JIT compile → Still slow → Eventually AOT
With baseline profile:
Install → AOT compile hot paths → First launch → Fast
What Goes in a Baseline Profile?
A baseline profile should contain:
- Startup path: The code that runs during app launch (Application.onCreate, first Activity, initial layout)
- Common user flows: The most frequent interactions (feed scrolling, navigation, search)
- Critical classes: Classes that are always loaded on startup
Do NOT include:
- Rarely used features
- Debug code
- Code paths that only run in specific conditions
Measuring Impact
You can measure the impact of baseline profiles using:
- Startup time:
adb shell am start -Wmeasures total startup time - Frame rendering: Systrace or Perfetto shows frame times
- Compilation status:
adb shell dumpsys package <pkg>shows compilation filters
The expected improvement is significant:
- Cold startup time reduction of 15-40%
- Fewer JIT compilations during first use
- Reduced jank in the first few seconds of interaction
- Lower memory usage from reduced JIT cache
Baseline Profile vs Compiler Config
The android:extractNativeLibs and compiler filter settings in build.gradle affect compilation:
android {
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
Baseline profiles complement these settings by providing runtime profile data that the compiler filter alone cannot capture.
Generating and Using Baseline Profiles
Macrobenchmark Setup
The baseline profile generator uses the Macrobenchmark library. Add it to your project:
// build.gradle.kts (project level)
plugins {
id("com.android.library") version "8.2.0" apply false
id("androidx.benchmark") version "1.2.0" apply false
}
// build.gradle.kts (benchmark module)
plugins {
id("com.android.library")
id("androidx.benchmark")
}
android {
namespace = "com.example.benchmark"
compileSdk = 34
defaultConfig {
minSdk = 24
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
dependencies {
implementation("androidx.test.ext:junit:1.1.5")
implementation("androidx.test.espresso:espresso-core:3.5.1")
implementation("androidx.benchmark:benchmark-macro-junit4:1.2.0")
implementation("androidx.benchmark:benchmark-common:1.2.0")
}
Writing a Profile Generator
Create a benchmark test that exercises the critical startup path:
@RunWith(AndroidJUnit4::class)
class ProfileGenerator {
@get:Rule
val rule = MacrobenchmarkRule()
@Test
fun generateStartupProfile() {
rule.measureRepeated(
packageName = "com.example.myapp",
metrics = listOf(StartupTimingMetric()),
compilationMode = CompilationMode.DEFAULT,
startupMode = StartupMode.COLD,
iterations = 10
) {
pressHome()
startActivityAndWait()
// Exercise common startup interactions
waitForIdle()
device.findObject(By.res("feed_list")).scroll(Direction.DOWN, 3f)
}
}
}
Running the Profile Generator
Execute the benchmark to generate the profile:
./gradlew :benchmark:connectedBenchmarkAndroidTest
After the benchmark runs, the profile is saved to src/main/baseline-prof.txt. This file contains rules in a specific format:
HSPLcom/example/myapp/MyApplication;->onCreate()V
Lcom/example/myapp/MyApplication;
Lcom/example/myapp/ui/MainActivity;
Lcom/example/myapp/ui/FeedFragment;
The rules are pattern-based:
H= hot method (called frequently)S= startup method (called during startup)P= post-startup methodL= class rule (include the entire class)
Automatic Profile Collection
For production apps, you can collect profiles from real users:
// In your Application class
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val profileInstaller = ProfileInstaller(context)
profileInstaller.collectProfile()
}
Google Play also provides profile data through the Baseline Profile Gradle plugin, which can aggregate profiles from your testing and production builds.
Verifying the Impact
After generating and installing the baseline profile, verify the improvement:
# Check compilation status
adb shell dumpsys package com.example.myapp | grep -i "compilation"
# Measure cold startup time
adb shell am force-stop com.example.myapp
adb shell am start -W com.example.myapp/.MainActivity
The compilation status should show speed-profile instead of quicken or verify, indicating AOT compilation based on the profile.
Quiz
1. What is the primary purpose of a baseline profile in Android?
2. Which library is used to generate baseline profiles in Android?
3. What compilation filter does ART use when a baseline profile is available?
4. What should NOT be included in a baseline profile?
Flashcards
Question
What is a baseline profile in Android?
Click to reveal answer
Answer
A file that tells ART which code paths are critical, enabling ahead-of-time compilation at install time for faster startup and smoother initial interactions.
Question
How do baseline profiles improve startup time?
Click to reveal answer
Answer
They allow ART to AOT compile hot code paths before the user opens the app, eliminating the slow interpret-then-JIT cycle on first launch.
Question
What library generates baseline profiles?
Click to reveal answer
Answer
The Macrobenchmark library from AndroidX. It runs automated benchmarks that exercise critical app paths and outputs a baseline-prof.txt file.
Question
What does the speed-profile compilation filter do?
Click to reveal answer
Answer
It AOT compiles only the methods identified in the profile as hot or startup, balancing install speed with runtime performance.
Revision Notes
Key Takeaways
- 1. Baseline profiles enable AOT compilation of critical code paths at install time
- 2. They significantly improve cold startup time and reduce first-use jank
- 3. The Macrobenchmark library generates profiles by exercising critical app paths
- 4. Include startup code and common flows, exclude debug and rarely-used code
- 5. Verify with speed-profile compilation filter in dumpsys output
Interview Tips
- • Explain the difference between interpretation, JIT, and AOT compilation in ART
- • Describe how baseline profiles change the compilation strategy
- • Discuss what code paths should be included in a baseline profile
- • Explain how to measure the impact of baseline profiles on startup time
- • Know the difference between speed-profile and speed compilation filters
Cheat Sheet
Baseline Profiles Cheat Sheet
What they are: Pre-built profiles of critical code paths for ART AOT compilation.
Why they matter:
- Without: First launch is slow (interpret → JIT → eventually AOT)
- With: First launch is fast (AOT compiled at install)
- 15-40% cold startup improvement
What to include:
- Startup path (Application.onCreate, first Activity)
- Common user flows (feed scrolling, navigation)
- Critical classes loaded on startup
What to exclude:
- Rarely used features
- Debug-only code
- Conditional code paths
Generation:
- Use Macrobenchmark library
- Write startup benchmarks
- Run: ./gradlew :benchmark:connectedBenchmarkAndroidTest
- Output: src/main/baseline-prof.txt
Verification:
- adb shell dumpsys package
| grep compilation - Should show speed-profile
- adb shell am start -W for startup timing