DEX layout optimizations and startup profiles

This documentation describes steps to enable DEX layout optimizations with startup profiles when building an Android app.

Requirements

To use DEX layout optimizations and related functionalities, you need Jetpack Macrobenchmarks version 1.2.0-alpha14 or later and the Android Gradle plugin version described in the following table:

Technology Minimum Android Gradle plugin version
Baseline Profiles 7.4.1
Dex Layout Optimizations 8.2.0-alpha09
Improved profile compilation 8.2.0-alpha09

Currently, third party build systems such as Bazel aren't supported.

Startup profiles

Startup profiles are similar to Baseline Profiles. They describe classes and methods critical to app startup that must be ready to load first. Startup profiles also use the same human readable format (HRF) that Baseline Profiles use.

A key difference between startup profiles and Baseline Profiles is that Baseline Profiles include classes and methods that are important for optimization beyond app startup—for example, reducing jank during animations or key Critical User Journeys (CUJs) beyond app startup.

Another key difference between startup profiles and Baseline Profiles is that startup profiles can't be provided by libraries and aren't subject to merging by the Android Gradle plugin. This is because libraries don't necessarily have a good understanding of what the critical classes and methods are for app startup. These classes and methods are best derived from Jetpack Macrobenchmarks, using the BaselineProfileRule with CUJs that specifically target app startup—such as collectStableBaselineProfile.

See the following table for a quick summary of the key differences between Baseline and startup profiles.

Profile type Per build Subject to merging
Baseline Profiles N Y
Startup Profiles Y N

Startup profiles are stored in src/<variantName>/main/baselineProfiles/startup-prof.txt.

DEX layout optimizations

This optimization helps reduce the number of major page faults during app startup by improving the locality of code used during startup, leading to faster startup times.

This is done by adding all code executed during startup into the primary classes.dex file while removing all non-startup code to outside of the primary classes.dex file.

Figure 1. Code locality improvement for DEX layout optimization.

Use DEX layout optimizations

This section shows how to use DEX layout optimizations.

Build startup profiles

First, you need to build your startup profiles.

Project setup

In the preceding example, the instrumentation test drives an app module with the package name com.example.app.

When you use Jetpack Macrobenchmarks to generate startup profile rules, you define a new benchmark variant as part of the build. The benchmark variant is identical to the release variant, except that you turn off minification.

The following is an excerpt from the app module's build.gradle file.

Kotlin

buildTypes {
        ...
        create("benchmark") {
            initWith(buildTypes.getByName("release")
            isMinifyEnabled = false
            signingConfig = signingConfigs.getByName("debug")
            matchingFallbacks += "release"
        }
}

Groovy

buildTypes {
        ...
        benchmark {
            initWith buildTypes.release
            minifyEnabled false
            signingConfig signingConfigs.debug
            matchingFallbacks = ['release']
        }
}

Generate startup rules

Kotlin

@RunWith(AndroidJUnit4::class)
class StartupProfileGenerator {
    @get:Rule
    val baselineProfileRule = BaselineProfileRule()

    @Test
    fun startup() =
        baselineProfileRule.collectStableBaselineProfile(
            packageName = "com.example.app",
            includeInStartupProfile = true
    ) {
            // This scenario just starts the activity and waits for it to draw
            // the first frame. If you have animations or async content in your
            // startup, wait for them with UiAutomator.
            startActivityAndWait()
        }
}

Java

@RunWith(AndroidJUnit4.class)
public class StartupProfileGenerator {
    @Rule
    BaselineProfileRule baselineProfileRule = new BaselineProfileRule();

    @Test
    public void startup() {
        baselineProfileRule.collectStableBaselineProfile(
            "com.example.app",
            /* includeInStartupProfile = */ true,
            (scope -> {
                // This scenario just starts the activity and waits for it to
                // draw the first frame. If you have animations or async content
                // in your startup, wait for them with UiAutomator.
                scope.startActivityAndWait();
                return null;
            }
        ));
    }
}

Output

Running the test results in a file that looks something like this:

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;
  • Copy the contents of the output file to src/main/baselineProfiles/startup-prof.txt.

Enable DEX layout optimizations

To enable DEX layout optimizations, use the following Gradle snippet:

Kotlin

android {
    // ...
    experimentalProperties["android.experimental.art-profile-r8-rewriting"] = true
    experimentalProperties["android.experimental.r8.dex-startup-optimization"] = true
}

Groovy

android {
    // ...
    experimentalProperties["android.experimental.art-profile-r8-rewriting"] = true
    experimentalProperties["android.experimental.r8.dex-startup-optimization"] = true
}

android.experimental.art-profile-r8-rewriting

The experimental property android.experimental.art-profile-r8-rewriting enables rewriting of baseline rules by D8 and R8.

This step is necessary because D8 and R8 perform optimizations like class merging, synthetics, and re-writing lambdas and method signature optimizations.

These optimizations can change the method signatures of an existing class and create new classes derived from the existing source symbols. When these transformations are performed, D8 and R8 perform the same set of transformations on the human readable baseline and startup profile rules.

This step is important to fully capture all the rules you need to optimize app performance. Enabling R8 rewriting of rules improves profile quality by as much as 25%.

android.experimental.r8.dex-startup-optimization

The experimental property android.experimental.r8.dex-startup-optimization enables DEX layout optimization.

  • To build the APK with DEX layout optimizations, you can use the following:
./gradlew :app:assembleRelease