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"