Skip to content

Commit

Permalink
Add support to generate baseline profiles for lottie and lottie-compo…
Browse files Browse the repository at this point in the history
…se (#2404)

Fixes #2338
  • Loading branch information
gpeal committed Oct 29, 2023
1 parent 34aa06b commit e5ac71e
Show file tree
Hide file tree
Showing 36 changed files with 1,218 additions and 677 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
.idea/runConfigurations

# Generated files
.idea/**/contentModel.xml
Expand Down
7 changes: 7 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added After Effects Samples/Benchmark.aep
Binary file not shown.
50 changes: 50 additions & 0 deletions app-benchmark/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import static de.fayard.refreshVersions.core.Versions.versionFor

plugins {
id 'com.android.application'
id "org.jetbrains.kotlin.android"
id 'androidx.baselineprofile'
}

android {
namespace 'com.airbnb.lottie.benchmark.app'
compileSdk 34
defaultConfig {
applicationId "com.airbnb.lottie.benchmark.app"
minSdk 21
targetSdk 34
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
signingConfig signingConfigs.debug
debuggable false
proguardFiles("proguard-rules.pro")
}
create("benchmark") {
initWith(release)
signingConfig = signingConfigs.getByName("debug")
}
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion = versionFor(project, AndroidX.compose.compiler)
}
}

dependencies {
implementation project(':lottie-compose')
implementation libs.androidx.appcompat
implementation libs.androidx.activity.compose
implementation platform(libs.compose.bom)
implementation libs.compose.ui
implementation libs.compose.material
implementation libs.compose.material.icons.extended
implementation libs.compose.ui.tooling
// Need this to side load a Baseline Profile when Benchmarking
implementation libs.profileinstaller
}
7 changes: 7 additions & 0 deletions app-benchmark/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Proguard rules that are applied to your test apk/code.
-dontoptimize
-dontobfuscate
-dontshrink
-ignorewarnings
-dontwarn *
-dontnote *
19 changes: 19 additions & 0 deletions app-benchmark/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<profileable android:shell="true" />

<activity
android:name=".BenchmarkActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.airbnb.lottie.benchmark.app

import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.animateLottieCompositionAsState
import com.airbnb.lottie.compose.rememberLottieComposition

class BenchmarkActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Content()
}
}

@Composable
fun Content() {
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.benchmark))
val progress by animateLottieCompositionAsState(composition, iterations = LottieConstants.IterateForever)
LottieAnimation(composition, { progress })
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions app-benchmark/src/main/res/raw/benchmark.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions app-benchmark/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<resources>
<string name="app_name">Lottie Benchmark</string>
</resources>
1 change: 1 addition & 0 deletions baselineprofile/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
43 changes: 43 additions & 0 deletions baselineprofile/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import com.android.build.api.dsl.ManagedVirtualDevice

plugins {
id 'com.android.test'
id 'org.jetbrains.kotlin.android'
id 'androidx.baselineprofile'
}

android {
namespace 'com.airbnb.lottie.baselineprofile'
compileSdk 34

defaultConfig {
minSdk 28
targetSdk 34

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments["androidx.benchmark.suppressErrors"] = "EMULATOR"
}

targetProjectPath = ":app-benchmark"

testOptions.managedDevices.devices {
pixel6Api34(ManagedVirtualDevice) {
device = "Pixel 6"
apiLevel = 34
systemImageSource = "google"
}
}
}

baselineProfile {
managedDevices += "pixel6Api34"
useConnectedDevices = false
}

dependencies {
implementation libs.okio
implementation libs.androidx.test.junit
implementation libs.androidx.test.espresso
implementation libs.androidx.test.uiautomator
implementation libs.androidx.test.macrobenchmark
}
1 change: 1 addition & 0 deletions baselineprofile/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<manifest />
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.airbnb.lottie.baselineprofile

import androidx.benchmark.macro.junit4.BaselineProfileRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

/**
* You can run the generator with the Generate Baseline Profile gradle task.
* ```
* ./gradlew :lottie(-compose):generateReleaseBaselineProfile -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile
* ```
*
* After you run the generator, you can verify the improvements running the [StartupBenchmarks] benchmark.
**/
@RunWith(AndroidJUnit4::class)
@LargeTest
class BaselineProfileGenerator {

@get:Rule
val rule = BaselineProfileRule()

@Test
fun generate() {
rule.collect("com.airbnb.lottie.benchmark.app") {
pressHome()
startActivityAndWait()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.airbnb.lottie.baselineprofile

import androidx.benchmark.macro.BaselineProfileMode
import androidx.benchmark.macro.CompilationMode
import androidx.benchmark.macro.StartupMode
import androidx.benchmark.macro.StartupTimingMetric
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

/**
* This test class benchmarks the speed of app startup.
* Run this benchmark to verify how effective a Baseline Profile is.
* It does this by comparing [CompilationMode.None], which represents the app with no Baseline
* Profiles optimizations, and [CompilationMode.Partial], which uses Baseline Profiles.
*
* Run this benchmark to see startup measurements and captured system traces for verifying
* the effectiveness of your Baseline Profiles. You can run it directly from Android
* Studio as an instrumentation test, or run all benchmarks with this Gradle task:
* ```
* ./gradlew :baselineprofile:connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=Macrobenchmark
* ```
*
* You should run the benchmarks on a physical device, not an Android emulator, because the
* emulator doesn't represent real world performance and shares system resources with its host.
*
* For more information, see the [Macrobenchmark documentation](https://d.android.com/macrobenchmark#create-macrobenchmark)
* and the [instrumentation arguments documentation](https://d.android.com/topic/performance/benchmarking/macrobenchmark-instrumentation-args).
*
* Note that the performance impact of this test is only tangentially related to the impact of the baseline profile for Lottie.
* The benefit of the baseline profile for Lottie is less about startup time and more about time spent running the hot path
* of the Lottie rendering code.
*
* Ideally, this test would be updated to reflect that rather than just startup time but that's a task for another time…
**/
@RunWith(AndroidJUnit4::class)
@LargeTest
class StartupBenchmarks {

@get:Rule
val rule = MacrobenchmarkRule()

@Test
fun startupCompilationNone() =
benchmark(CompilationMode.None())

@Test
fun startupCompilationBaselineProfiles() =
benchmark(CompilationMode.Partial(BaselineProfileMode.Require))

private fun benchmark(compilationMode: CompilationMode) {
rule.measureRepeated(
packageName = "com.airbnb.lottie.benchmark.app",
metrics = listOf(StartupTimingMetric()),
compilationMode = compilationMode,
startupMode = StartupMode.COLD,
iterations = 10,
setupBlock = {
pressHome()
},
measureBlock = {
startActivityAndWait()
}
)
}
}
52 changes: 52 additions & 0 deletions benchmark/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
plugins {
id 'com.android.test'
id 'org.jetbrains.kotlin.android'
id 'androidx.baselineprofile'
}

android {
namespace 'com.airbnb.lottie.benchmark'
compileSdk 34

kotlinOptions {
freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
}

defaultConfig {
minSdk 30
targetSdk 34

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
debuggable = true
signingConfig = debug.signingConfig
}
}

targetProjectPath = ":app-benchmark"
experimentalProperties["android.experimental.self-instrumenting"] = true

testOptions.managedDevices.devices {
pixel6Api31(com.android.build.api.dsl.ManagedVirtualDevice) {
device = "Pixel 6"
apiLevel = 31
systemImageSource = "aosp"
}
}
}

baselineProfile {
managedDevices += "pixel6Api31"
useConnectedDevices = false
}

dependencies {
implementation libs.androidx.test.junit
implementation libs.androidx.test.espresso
implementation libs.androidx.test.uiautomator
implementation libs.androidx.test.macrobenchmark
implementation libs.compose.ui.test.junit
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
xmlns:tools="http://schemas.android.com/tools">

<queries>
<package android:name="com.airbnb.lottie.sample.compose" />
<package android:name="com.airbnb.lottie.benchmark.app" />
</queries>
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
</manifest>
</manifest>
Loading

0 comments on commit e5ac71e

Please sign in to comment.