Baseline Profiles

Baseline Profiles are a list of classes and methods included in an APK used by Android Runtime (ART) during installation to pre-compile critical paths to machine code. This is a form of profile guided optimization (PGO) that lets apps optimize startup, reduce jank, and improve performance for end users.

How Baseline Profiles work

Profile rules are compiled into a binary form in the APK, in assets/dexopt/baseline.prof.

During installation, ART performs Ahead-of-time (AOT) compilation of methods in the profile, resulting in those methods executing faster. If the profile contains methods used in app launch or during frame rendering, the user experiences faster launch times and/or reduced jank.

While developing your app or library, consider defining Baseline Profiles to cover specific hot paths during critical user journeys where rendering time or latency are important, such as startup, transitions, or scrolling.

Baseline Profiles are then shipped directly to users (through Google Play) along with the APK.

This diagram displays the baseline profile workflow from upload through end-user delivery, and how that workflow relates to cloud profiles.

Reasons for using Baseline Profiles

Startup time is a critical component to improve user engagement with your application. Increasing the speed and responsiveness of an app leads to more daily active users and a higher average return visit rate.

Cloud Profiles also optimize these same interactions, but are only available to users a day or more after an update is released and don't support Android 7 (API 24) up to Android 8 (API 26).

Compilation behavior across Android versions

Android Platform versions have used different app compilation approaches, each with a corresponding performance tradeoff. Baseline profiles improve upon the previous compilation methods by providing a profile for all installs.

Android version Compilation method Optimization approach
Android 5 (API level 21) up to Android 6 (API level 23) Full AOT The entire app is optimized during install, resulting in long wait times to use the app, increased RAM and disk space usage, and longer times to load code from disk, potentially increasing cold startup times.
Android 7 (API level 24) up to Android 8.1 (API level 27) Partial AOT (Baseline Profile) Baseline Profiles are installed by androidx.profileinstaller on the first run, when the app module defines this dependency. ART may improve this further by adding additional profile rules during the app's use and compiling them when the device is idle. This optimizes for disk space and time to load code from the disk, thereby reducing wait time for the app.
Android 9 (API level 28) and higher Partial AOT (Baseline + Cloud Profile) Play uses Baseline Profiles during app installs to optimize the APK and Cloud profiles (if available). After installation, ART profiles are uploaded to Play and aggregated, then provided as Cloud Profiles to other users when they install/update the app.

Create Baseline Profiles

Create profile rules automatically using BaselineProfileRule

As an app developer, you can automatically generate profiles for every app release by using the Jetpack Macrobenchmark library.

To create Baseline Profiles using the Macrobenchmark library:

  1. Add a dependency to the ProfileInstaller library in your app's build.gradle to enable local and Play Store Baseline Profile compilation. This is the only way to sideload a Baseline Profile locally.

    dependencies {
         implementation("androidx.profileinstaller:profileinstaller:1.2.0-beta01")
    }
    
  2. Set up a Macrobenchmark module in your gradle project.

  3. Define a new test called BaselineProfileGenerator that looks something like:

    @ExperimentalBaselineProfilesApi
    @RunWith(AndroidJUnit4::class)
    class BaselineProfileGenerator {
        @get:Rule val baselineProfileRule = BaselineProfileRule()
    
        @Test
        fun startup() =
            baselineProfileRule.collectBaselineProfile(packageName = "com.example.app") {
                pressHome()
                // This block defines the app's critical user journey. Here we are interested in
                // optimizing for app startup. But you can also navigate and scroll
                // through your most important UI.
                startActivityAndWait()
            }
    }
    
  4. Connect a userdebug or rooted Android Open Source Project (AOSP) emulator running Android 9 or higher.

  5. Run adb root command from terminal to ensure the adb daemon is running with root permissions.

  6. Run the test and wait for its completion.

  7. Find the generated profile location in logcat. Search for the log tag Benchmark.

    com.example.app D/Benchmark: Usable output directory: /storage/emulated/0/Android/media/com.example.app
    
    # List the output baseline profile
    ls /storage/emulated/0/Android/media/com.example.app
    SampleStartupBenchmark_startup-baseline-prof.txt
    
  8. Pull the generated file from your device.

    adb pull storage/emulated/0/Android/media/com.example.app/SampleStartupBenchmark_startup-baseline-prof.txt .
    
  9. Rename the generated file to baseline-prof.txt and copy it to the src/main directory of your app module.

Define profile rules manually

You can define profile rules manually in an app or a library module by creating a file called baseline-prof.txt located in the src/main directory. This is the same folder that contains the AndroidManifest.xml file.

The file specifies one rule per line. Each rule represents a pattern for matching methods or classes in the app or library that needs to be optimized.

The syntax for these rules is a superset of the human-readable ART profile format (HRF) when using adb shell profman --dump-classes-and-methods. The syntax is very similar to the syntax for descriptors and signatures, but also allows wildcards to simplify the rule-writing process.

The following examples shows a few Baseline Profile rules included in the Jetpack Compose library:

HSPLandroidx/compose/runtime/ComposerImpl;->updateValue(Ljava/lang/Object;)V
HSPLandroidx/compose/runtime/ComposerImpl;->updatedNodeCount(I)I
HLandroidx/compose/runtime/ComposerImpl;->validateNodeExpected()V
PLandroidx/compose/runtime/CompositionImpl;->applyChanges()V
HLandroidx/compose/runtime/ComposerKt;->findLocation(Ljava/util/List;I)I
Landroidx/compose/runtime/ComposerImpl;

Rule syntax

These rules take one of two forms to target either methods or classes:

[FLAGS][CLASS_DESCRIPTOR]->[METHOD_SIGNATURE]

A class rule uses the following pattern:

[CLASS_DESCRIPTOR]
Syntax Description
FLAGS Represents one or more of the characters H, S, and P to indicate whether this method should be flagged as Hot, Startup, or Post Startup in regards to the startup type.

A method with the H flag indicates that it is a "hot" method, meaning it is called many times during the lifetime of the app.

A method with the S flag indicates that it is a method called at startup.

A method with the P flag indicates that it is a hot method not related to startup.

A class present in this file indicates that it is used during startup and should be pre-allocated in the heap to avoid the cost of class loading. ART compiler employs various optimization strategies, such as AOT compilation of these methods and performing layout optimizations in the generated AOT file.
CLASS_DESCRIPTOR Descriptor for the targeted method's class. For instance, androidx.compose.runtime.SlotTable would have a descriptor of Landroidx/compose/runtime/SlotTable;. Note: L is prepended here per the Dalvik Executable (DEX) format.
METHOD_SIGNATURE Signature of the method, including the name, parameter types, and return types of the method. For example, the method

// LayoutNode.kt

fun isPlaced():Boolean {
// ...
}

on LayoutNode has the signature isPlaced()Z.

These patterns can have wildcards in order to have a single rule encompass multiple methods or classes. For guided assistance when writing with rule syntax in Android Studio, take a look at the Android Baseline Profiles plugin.

An example of a wildcard rule might look something like this:

HSPLandroidx/compose/ui/layout/**->**(**)**

Supported types in baseline profile rules

Baseline Profile rules support the following types. For details on these types, see the Dalvik Executable (DEX) format.

Character Type Description
B byte Signed byte
C char Unicode character code point encoded in UTF-16
D double Double-precision floating point value
F float Single-precision floating point value
I int Integer
J long Long integer
S short Signed short
V void Void
Z boolean True or false
L (class name) reference An instance of a class name

Additionally, libraries can define rules that will be packaged in AAR artifacts. When you build an APK to include these artifacts, the rules are merged together (similar to how manifest merging is done) and compiled to a compact binary ART profile that is specific to the APK.

ART leverages this profile when the APK is used on devices to AOT compile a specific subset of the application at install-time on Android 9 (API level 28), or Android 7 (API level 24) when using ProfileInstaller.

Additional notes

When creating Baseline Profiles, there are some additional things to note:

  • Android 5 up to Android 6 (API levels 21 and 23) already AOT compile the APK at install time.

  • Debuggable applications are never AOT compiled to help with troubleshooting.

  • Rule files must be named baseline-prof.txt and placed in the root directory of your main source set (it should be a sibling file to your AndroidManifest.xml file).

  • These files will only be utilized if you are using Android Gradle Plugin 7.1.0-alpha05 or above (Android Studio Bumblebee Canary 5).

  • Bazel does not currently support reading and merging Baseline Profiles into an APK.

  • Baseline Profiles cannot be larger than 1.5 MB compressed. Therefore, libraries and applications should strive to define a small set of profile rules which maximize impact.

  • Broad rules that compile too much of the application can slow down startup due to increased disk access. You should test the performance of your Baseline Profiles.

Measure improvements

Automate measurement with the Macrobenchmark library

Macrobenchmarks let you control pre-measurement compilation via the CompilationMode API, including BaselineProfile usage.

If you've already set up a BaselineProfileRule test in a Macrobenchmark module, you can define a new test in that module to evaluate its performance:

@RunWith(AndroidJUnit4::class)
class BaselineProfileBenchmark {
  @get:Rule
  val benchmarkRule = MacrobenchmarkRule()

  @Test
  fun startupNoCompilation() {
    startup(CompilationMode.None())
  }

  @Test
  fun startupBaselineProfile() {
    startup(CompilationMode.Partial(
      baselineProfileMode = BaselineProfileMode.Require
    ))
  }

  private fun startup(compilationMode: CompilationMode) {
    benchmarkRule.measureRepeated(
      packageName = "com.example.app",
      metrics = listOf(StartupTimingMetric()),
      iterations = 10,
      startupMode = StartupMode.COLD,
      compilationMode = compilationMode
    ) { // this = MacrobenchmarkScope
        pressHome()
        startActivityAndWait()
    }
  }
}

An example of passing test results are seen here:

These are the results of a small test. Larger apps will see greater benefits from Baseline Profiles.

Note, while the example above looks at StartupTimingMetric, there are other important metrics worth considering, like Jank (Frame metrics), which can be measured using Jetpack Macrobenchmark.

Manually measure app improvements

First, let's measure the unoptimized app startup for reference.

PACKAGE_NAME=com.example.app
# Force Stop App
adb shell am force-stop $PACKAGE_NAME
# Reset compiled state
adb shell cmd package compile --reset $PACKAGE_NAME
# Measure App startup
# This corresponds to `Time to initial display` metric
# For additional info https://developer.android.com/topic/performance/vitals/launch-time#time-initial
adb shell am start-activity -W -n $PACKAGE_NAME/.ExampleActivity \
 | grep "TotalTime"

Next, let's sideload the Baseline Profile.

# Unzip the Release APK first
unzip release.apk
# Create a ZIP archive
# Note: The name should match the name of the APK
# Note: Copy baseline.prof{m} and rename it to primary.prof{m}
cp assets/dexopt/baseline.prof primary.prof
cp assets/dexopt/baseline.profm primary.profm
# Create an archive
zip -r release.dm primary.prof primary.profm
# Confirm that release.dm only contains the two profile files:
unzip -l release.dm
# Archive:  release.dm
#   Length      Date    Time    Name
# ---------  ---------- -----   ----
#      3885  1980-12-31 17:01   primary.prof
#      1024  1980-12-31 17:01   primary.profm
# ---------                     -------
#                               2 files
# Install APK + Profile together
adb install-multiple release.apk release.dm

To verify that the package was optimized on install, run the following command:

# Check dexopt state
adb shell dumpsys package dexopt | grep -A 1 $PACKAGE_NAME

The output should state that the package was compiled.

[com.example.app]
  path: /data/app/~~YvNxUxuP2e5xA6EGtM5i9A==/com.example.app-zQ0tkJN8tDrEZXTlrDUSBg==/base.apk
  arm64: [status=speed-profile] [reason=install-dm]

Now, we can measure app startup performance like we did before, but without resetting compiled state.

# Force Stop App
adb shell am force-stop $PACKAGE_NAME
# Measure App startup
adb shell am start-activity -W -n $PACKAGE_NAME/.ExampleActivity \
 | grep "TotalTime"

Known Issues

Currently, using Baseline Profiles has several known issues:

  • Baseline profiles are not packaged correctly when building the APK from an app bundle. To resolve this issue, apply com.android.tools.build:gradle:7.3.0-beta02 and higher (issue).

  • Baseline profiles are only correctly packaged for the primary classes.dex file. This affects apps with more than one .dex file. To resolve this issue, apply com.android.tools.build:gradle:7.3.0-beta02 and higher (issue).

  • Macrobenchmark is incompatible with Baseline Profiles on Android 12L (API 32) (issue) and Android 13 (API 33) (issue).

  • Resetting ART profile caches is not allowed on user (non-rooted) builds. To work around this, androidx.benchmark:benchmark-macro-junit4:1.1.0-rc02 includes a fix that reinstalls the app during the benchmark (issue).

  • Android Studio Profilers don't install Baseline Profiles when profiling the app (issue).

  • Non Gradle build systems (Bazel, Buck, etc.) don't support compiling Baseline Profiles into output APKs.