使用 Jetpack Macrobenchmark 对用例进行基准测试

借助 Macrobenchmark,您可以直接针对在搭载 Android M (API 23) 或更高版本系统的设备上运行的应用编写启动和运行时性能测试。

建议您将 Macrobenchmark 与最新版本的 Android Studio(2021.1.1 或更高版本)搭配使用,因为该版本的 IDE 中提供了可与 Macrobenchmark 集成的新功能。使用早期版本 Android Studio 的用户可以根据本主题后面部分的额外说明来处理跟踪文件。

基准测试通过 Macrobenchmark 库中的 MacrobenchmarkRule JUnit4 规则 API 提供:

Kotlin

    @get:Rule
    val benchmarkRule = MacrobenchmarkRule()

    @Test
    fun startup() = benchmarkRule.measureRepeated(
        packageName = "mypackage.myapp",
        metrics = listOf(StartupTimingMetric()),
        iterations = 5,
        startupMode = StartupMode.COLD
    ) { // this = MacrobenchmarkScope
        pressHome()
        val intent = Intent()
        intent.setPackage("mypackage.myapp")
        intent.setAction("mypackage.myapp.myaction")
        startActivityAndWait(intent)
    }
  

Java

    @Rule
    MacrobenchmarkRule benchmarkRule = MacrobenchmarkRule()

    @Test
    void startup() = benchmarkRule.measureRepeated(
        "mypackage.myapp", // packageName
        listOf(StartupTimingMetric()), // metrics
        5, // iterations
        StartupMode.COLD // startupMode
    ) { scope ->
        scope.pressHome()
        Intent intent = Intent()
        intent.setPackage("mypackage.myapp")
        intent.setAction("mypackage.myapp.myaction")
        scope.startActivityAndWait(intent)
    }
  

指标会直接显示在 Android Studio 中,还会针对使用 CI 的情况以 JSON 文件形式输出。

Studio 结果示例

模块设置

宏基准需要一个独立于应用代码的 com.android.test 模块来负责运行衡量应用的测试。

Bumblebee

在 Android Studio Bumblebee 中,我们提供了一个模板来简化 Macrobenchmark 模块设置。

添加新模块

基准化分析模块模板会自动在项目中创建一个模块(包含一个示例启动基准),用于衡量通过应用模块构建的应用。

如需使用模块模板创建新模块,请执行以下操作:

  1. 在 Android Studio 的 Project 面板中右键点击您的项目或模块,然后依次点击 New > Module

  2. 选择 Benchmark Module

    基准模块模板

  3. 您可以自定义目标应用(要进行基准测试的应用),以及新的 Macrobenchmark 模块的软件包和模块名称。

  4. 点击 Finish

Arctic Fox

在 Arctic Fox 中,您要创建一个库模块,然后将其转换为测试模块。

添加新模块

向项目添加新模块。此模块将包含您的 Macrobenchmark 测试。

  1. 在 Android Studio 的 Project 面板中右键点击您的项目或模块,然后依次点击 New > Module
  2. Templates 窗格中选择 Android Library
  3. 输入 macrobenchmark 作为模块名称。
  4. Minimum SDK 设置为 API 23: Android M
  5. 点击 Finish

配置新的库模块

修改 Gradle 文件

按如下所示自定义 Macrobenchmark 模块的 build.gradle

  1. 将插件从 com.android.library 更改为 com.android.test
  2. android {} 代码块中添加其他所需的测试模块属性:
  3.    targetProjectPath = ":app" // Note that your module name may be different
    
       // Enable the benchmark to run separately from the app process
       experimentalProperties["android.experimental.self-instrumenting"] = true
       buildTypes {
           // Declare a build type (release) to match the target app's build type
           release {
               debuggable = true
           }
       }
  4. 将所有名为 testImplementationandroidTestImplementation 的依赖项更改为 implementation
  5. 添加 Macrobenchmark 库的依赖项:
    • implementation 'androidx.benchmark:benchmark-macro-junit4:1.1.0-alpha13'
  6. android {} 代码块之后、dependencies {} 代码块之前,添加以下代码:
  7.    androidComponents {
          beforeVariants(selector().all()) {
              // Enable only the benchmark buildType, since we only want to measure
              // release-like build performance (should match app buildType)
              enabled = buildType == 'benchmark'
          }
       }

简化目录结构

com.android.test 模块中,对于所有测试,只有一个源目录。删除包括 src/testsrc/androidTest 在内的其他源目录,因为系统不会使用这些目录。

有关参考信息,请参阅 Macrobenchmark 模块示例

创建宏基准

在该模块中定义一个新的测试类,并填入应用的软件包名称:

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

    @Test
    fun startup() = benchmarkRule.measureRepeated(
        packageName = "mypackage.myapp",
        metrics = listOf(StartupTimingMetric()),
        iterations = 5,
        startupMode = StartupMode.COLD
    ) { // this = MacrobenchmarkScope
        pressHome()
        val intent = Intent()
        intent.setPackage("mypackage.myapp")
        intent.setAction("mypackage.myapp.myaction")
        startActivityAndWait(intent)
    }
}
   

设置应用

若要对应用(称为该宏基准测试的目标)进行基准测试,该应用必须处于可分析状态,这样系统才能读取详细的跟踪信息。您可以在应用的 AndroidManifest.xml<application> 标记中启用此状态:

<application ... >
    <!-- Profileable to enable Macrobenchmark profiling -->
    <!-- Suppress AndroidElementNotAllowed -->
    <profileable android:shell="true"/>
    ...
</application>

尽可能将进行基准测试的应用配置得接近用户体验。将其设置为不可调试,最好开启“缩减大小”功能,以提高性能。通常,您可以通过创建 release 变体的 benchmark 副本来实现此目的,该副本将执行同样的操作,但使用 debug 密钥在本地签名:

buildTypes {
    benchmark {
        // duplicate any release build type settings for measurement accuracy,
        // such as "minifyEnabled" and "proguardFiles" in this block

        debuggable false
        signingConfig signingConfigs.debug
    }
}

执行 Gradle 同步,打开左侧的 Build Variants 面板,然后选择应用和 Macrobenchmark 模块的 benchmark 变体。 这可确保运行基准测试的操作将会构建并测试应用的正确变体:

选择要进行基准测试的变体

针对内部 activity 运行 Macrobenchmark 需要执行一个额外步骤。如需对 exported=false 内部 activity 进行基准测试,需要将 setupBlock 传递给 MacrobenchmarkRule.measureRepeated(),以导航到要进行基准测试的代码,并使用 measureBlock 来调用要衡量的实际 activity 启动或滚动操作。

自定义宏基准

CompilationMode

宏基准可以指定 CompilationMode,用于定义应该将应用的多大部分从 DEX 字节码(APK 中的字节码格式)预编译为机器代码(类似于预编译的 C++)。

默认情况下,系统会使用 CompilationMode.DEFAULT 运行宏基准,这会在 Android Nougat (API 24) 及更高版本上安装基准配置文件(如果有),并在 Android Marshmallow (API 23) 及更低版本上完全编译 APK(这是默认系统行为)。

如果目标应用同时包含基准配置文件和 ProfileInstaller 库,您可以安装基准配置文件。

在 Android Nougat (API 24) 及更高版本中,您可以自定义 CompilationMode 以影响设备上的预编译量,从而模拟不同级别的预先 (AOT) 编译或 JIT 缓存。请参阅 CompilationMode.FullCompilationMode.PartialCompilationMode.None

此功能基于 ART 编译命令构建而成。每次基准测试都会在开始之前清除性能分析数据,以确保基准测试之间互不干扰。

启动

如需执行 activity 启动,您可以向 measureRepeated() 函数传递一种预定义的启动模式(COLDWARMHOT 中的一种)。此参数会更改 activity 的启动方式,以及测试开始时的进程状态。

如需详细了解启动类型,请参阅 Android Vitals 启动文档

滚动和动画

与大多数 Android 界面测试不同,Macrobenchmark 测试在独立于应用本身的进程中运行。必须这样做才能实现终止应用进程和使用 shell 命令进行编译等目的。

您可以使用 UI Automator 库或其他可以通过测试进程控制目标应用的机制来驱动您的应用。Espresso 或 ActivityScenario 等方法不起作用,因为它们需要在与应用共享的进程中运行。

以下示例会使用资源 ID 查找 RecyclerView,然后向下滚动几次:

@Test
fun measureScroll() {
    benchmarkRule.measureRepeated(
        packageName = "mypackage.myapp",
        metrics = listOf(FrameTimingMetric()),
        iterations = 5,
        setupBlock = {
            // before starting to measure, navigate to the UI to be measured
            val intent = Intent()
            intent.action = ACTION
            startActivityAndWait(intent)
        }
    ) {
        val recycler = device.findObject(By.res("mypackage.myapp", "recycler_id"))
        // Set gesture margin to avoid triggering gesture nav
        // with input events from automation.
        recycler.setGestureMargin(device.displayWidth / 5)

        // Scroll down several times
        for (i in 1..10) {
            recycler.scroll(Direction.DOWN, 2f)
            device.waitForIdle()
        }
    }
}

由于测试指定了 FrameTimingMetric,因此系统会记录帧时间并以帧时间分布简略摘要的形式进行报告:50%、90%、95% 和 99%。

您的基准并不一定要滚动界面。例如,它也可以运行动画。此外,它也不需要特地使用 UI Automator;只要帧是由视图系统生成的,其中包括 Compose 生成的帧,系统就会收集性能指标。请注意,诸如 Espresso 之类的进程内机制不起作用,因为应用需要从测试应用进程驱动。

运行宏基准

在 Android Studio 中运行测试,以衡量应用在设备上的性能。请注意,必须在实体设备上运行测试,而不是在模拟器上,因为模拟器无法产生代表最终用户体验的性能数据。

如需了解如何在持续集成环境中运行和监控基准测试,请参阅在持续集成环境中运行基准测试部分。

您也可以执行 connectedCheck 命令,从命令行运行所有基准测试:

$ ./gradlew :macrobenchmark:connectedCheck

配置错误

如果应用配置有误(可调试或不可分析),Macrobenchmark 测试就会抛出错误,而不会报告不正确或不完整的衡量结果。 您可以使用 androidx.benchmark.suppressErrors 参数抑制这些错误。

如果尝试衡量模拟器,或在电量不足的设备上进行衡量,系统也会抛出错误,因为这可能会影响核心可用性和时钟速度。

检查跟踪文件

每次衡量的迭代均会捕获单独的系统跟踪文件。点击 Test Results 窗格中的其中一个链接,可以打开这些结果跟踪文件,如本主题 Jetpack Macrobenchmark 部分中的图片所示。跟踪文件加载完成后,Android Studio 会提示您选择要分析的进程。系统会预先填充目标应用进程:

Studio 跟踪进程选择

跟踪文件加载完成后,Studio 将在 CPU 性能剖析器工具中显示结果:

Studio 显示跟踪结果的界面

手动访问跟踪文件

如果您使用的是旧版 Android Studio(2020.3.1 之前的版本),或者要使用 Perfetto 工具分析跟踪文件,则还需要执行几个额外的步骤。

首先,从设备中提取跟踪文件:

# The following command pulls all files ending in .perfetto-trace from the directory
# hierarchy starting at the root /storage/emulated/0/Android.
$ adb shell find /storage/emulated/0/Android/ -name "*.perfetto-trace" \
    | tr -d '\r' | xargs -n1 adb pull

请注意,如果您使用 additionalTestOutputDir 参数自定义输出文件路径,则输出路径可能会有所不同。您可以在 logcat 中查找跟踪文件路径日志,了解它们的写入位置。例如:

I PerfettoCapture: Writing to /storage/emulated/0/Android/data/androidx.benchmark.integration.macrobenchmark.test/cache/TrivialStartupBenchmark_startup[mode=COLD]_iter002.perfetto-trace.

如果您改为使用 Gradle 命令行(例如 ./gradlew macrobenchmark:connectedCheck)调用测试,则可以将测试结果文件复制到主机系统上的测试输出目录。为此,请将以下行添加到项目的 gradle.properties 文件中:

android.enableAdditionalTestOutput=true

测试运行的结果文件会显示在项目的 build 目录中,如下所示:

build/outputs/connected_android_test_additional_output/debugAndroidTest/connected/<device-name>/TrivialStartupBenchmark_startup[mode=COLD]_iter002.perfetto-trace

将跟踪文件复制到主机系统后,您可以在 Android Studio 中通过依次点击 File > Open 菜单打开此跟踪文件。此时系统会显示性能剖析器工具视图,和上一部分中显示的一样。

此外,您也可以选择使用 Perfetto 工具。您可以使用 Perfetto 检查跟踪期间整个设备上发生的所有进程,而 Android Studio 的 CPU 性能剖析器只能检查单个进程。

利用自定义事件改进跟踪数据

利用自定义跟踪事件进行应用插桩非常有用,这些事件会显示在跟踪报告的其余部分,有助于找出应用特有的问题。如需详细了解如何创建自定义跟踪事件,请参阅定义自定义事件指南。

在持续集成环境中运行基准测试

不使用 Gradle 时,在持续集成环境中运行测试是很常见的,如果您使用的是其他构建系统,则还可能会在本地运行测试。本部分介绍了如何针对在运行期间使用持续集成的情况配置 Macrobenchmark。

结果文件:JSON 和跟踪文件

Macrobenchmark 会输出一个 JSON 文件和多个跟踪文件:每个 MacrobenchmarkRule.measureRepeated 循环的每次衡量迭代对应一个文件。

您可以通过在运行时传入以下插桩参数来定义这些文件的写入位置:

-e additionalTestOutputDir "device_path_you_can_write_to"

请注意,为简单起见,您可以指定一个 /sdcard/ 上的路径,但必须在 Macrobenchmark 模块中将 requestLegacyExternalStorage 设置为 true,以停用分区存储:

<manifest ... >
  <application android:requestLegacyExternalStorage="true" ... >
    ...
  </application>
</manifest>

或者,传递插桩参数以绕过分区存储进行测试:

-e no-isolated-storage 1

JSON 示例

下面显示了单次启动基准测试的 JSON 输出示例:

{
    "context": {
        "build": {
            "device": "walleye",
            "fingerprint": "google/walleye/walleye:10/QQ3A.200805.001/6578210:userdebug/dev-keys",
            "model": "Pixel 2",
            "version": {
                "sdk": 29
            }
        },
        "cpuCoreCount": 8,
        "cpuLocked": false,
        "cpuMaxFreqHz": 2457600000,
        "memTotalBytes": 3834605568,
        "sustainedPerformanceModeEnabled": false
    },
    "benchmarks": [
        {
            "name": "startup",
            "params": {},
            "className": "androidx.benchmark.integration.macrobenchmark.SampleStartupBenchmark",
            "totalRunTimeNs": 77969052767,
            "metrics": {
                "startupMs": {
                    "minimum": 228,
                    "maximum": 283,
                    "median": 242,
                    "runs": [
                        238,
                        283,
                        256,
                        228,
                        242
                    ]
                }
            },
            "warmupIterations": 3,
            "repeatIterations": 5,
            "thermalThrottleSleepSeconds": 0
        }
    ]
}

其他资源

GitHub 上提供了一个示例项目,该项目是 Android/performance-samples 代码库的一部分。

如需了解如何检测回归问题,请参阅在 CI 中使用基准来解决回归问题

反馈

如需针对 Jetpack Macrobenchmark 报告问题或提交功能请求,请参阅公开问题跟踪器