Create and measure Baseline Profiles without Macrobenchmark

We highly recommend automating generation of profile rules using the Jetpack Macrobenchmark library to reduce manual effort and increase general scalability. However, it is possible to manually create and measure profile rules in your app.

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.

Manually measure app improvements

We highly recommend that you measure app improvements through benchmarking. However, if you'd like to measure improvements manually, you can get started by measuring 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, 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, you can measure app startup performance like you did before, but without resetting the compiled state. Ensure that you don't reset the compiled state for the package.

# 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"