持续集成中的基准测试

您可以在持续集成 (CI) 中运行基准测试,以便在应用发布之前跟踪性能并识别性能回归(或改进)。本页面提供了在 CI 中进行基准测试的基本信息。

在 CI 中开始进行基准测试之前,请思考结果捕获和评估与常规测试有何不同。

模糊结果

虽然基准测试是检测测试,但结果不只是通过或失败。基准测试提供在其运行的给定设备上的时间测量值。随时间绘制结果图表可以监控变化并观察测量系统中的噪声。

使用真机

在 Android 真机上运行基准测试。虽然它们可以在模拟器上运行,但强烈不建议这样做,因为它不代表真实的用户体验,而是提供与主机操作系统和硬件功能相关的数据。考虑使用真机或允许您在真机上运行测试的服务,例如 Firebase Test Lab

运行基准测试

在 CI 流水线中运行基准测试可能与在 Android Studio 本地运行不同。在本地,您通常使用一个 Gradle connectedCheck 任务运行 Android 集成测试。此任务会自动构建您的 APK 和测试 APK,并在连接到 CI 服务器的设备上运行测试。在 CI 中运行此流程时,通常需要将其拆分为单独的阶段。

构建

对于 Microbenchmark 库,运行 Gradle 任务 assemble[VariantName]AndroidTest,它会创建您的测试 APK,其中包含您的应用代码和测试代码。

此外,Macrobenchmark 库要求您分别构建目标 APK 和测试 APK。因此,运行 :app:assemble[VariantName]:macrobenchmark:assemble[VariantName] Gradle 任务。

安装和运行

这些步骤通常无需运行 Gradle 任务即可完成。请注意,它们可能会根据您是否使用允许在真机上运行测试的服务而进行抽象。

对于安装,请使用 adb install 命令并指定测试 APK 或目标 APK。

运行 adb shell am instrument 命令以运行所有基准测试

adb shell am instrument -w com.example.benchmark/androidx.benchmark.junit4.AndroidBenchmarkRunner

使用 Macrobenchmark 库时,使用常规的 androidx.test.runner.AndroidJUnitRunner 作为检测运行器。

您可以使用 -e 参数传递与 Gradle 配置中相同的检测参数。有关所有检测参数选项,请参阅 Microbenchmark 检测参数添加检测参数(针对 Macrobenchmark)。

例如,您可以将 dryRunMode 参数设置为在拉取请求验证流程中运行微基准测试。启用此标志后,微基准测试将仅以单循环运行,以验证它们是否正确运行,但不会花费太长时间执行。

adb shell am instrument -w -e "androidx.benchmark.dryRunMode.enable" "true" com.example.benchmark/androidx.benchmark.junit4.AndroidBenchmarkRunner

如需了解如何从命令行运行检测测试的更多信息,请参阅 使用 ADB 运行测试

锁定 CPU 时钟

Microbenchmark Gradle 插件提供命令 ./gradlew lockClocks 以锁定已获得 root 权限的设备的 CPU 时钟。这在您能够访问已获得 root 权限的设备(例如“userdebug”构建)时,对于确保稳定性非常有用。您可以使用 lockClocks.sh shell 脚本复制此操作,该脚本在 库的源代码 中提供。

您可以直接从 Linux 或 Mac 主机运行脚本,也可以使用一些 adb 命令将其推送到设备

adb push path/lockClocks.sh /data/local/tmp/lockClocks.sh
adb shell /data/local/tmp/lockClocks.sh
adb shell rm /data/local/tmp/lockClocks.sh

如果您直接在主机上运行 shell 脚本,它会将这些命令分派给连接的设备。

如需了解为何锁定 CPU 时钟会有所帮助的更多信息,请参阅如何 获取一致的基准测试结果

收集结果

基准测试库以 JSON 格式输出测量结果,并在每次基准测试运行后将性能分析跟踪输出到 Android 设备上的目录。Macrobenchmark 库会输出多个 perfetto 跟踪文件:每个 MacrobenchmarkRule.measureRepeated 循环的每次测量迭代都会生成一个文件。Microbenchmark 不过,会为每个 BenchmarkRule.measureRepeated 的所有迭代创建一个跟踪文件。性能分析跟踪文件也输出到此同一目录。

保存和查找文件

如果您使用 Gradle 运行基准测试,这些文件会自动复制到主机计算机的以下输出目录:build/outputs/connected_android_test_additional_output/debugAndroidTest/connected/

如果直接使用 adb 命令运行,您需要手动提取文件。默认情况下,报告会保存在设备上,位于所测试应用外部存储的 media 目录中。为方便起见,该库会将文件的路径打印到 Logcat 中。请注意,输出文件夹可能因基准测试运行的 Android 版本而异。

Benchmark: writing results to /storage/emulated/0/Android/media/com.example.macrobenchmark/com.example.macrobenchmark-benchmarkData.json

您还可以使用检测参数 additionalTestOutputDir 配置基准测试报告在设备上的保存位置。此文件夹必须可由您的应用写入。

adb shell am instrument -w -e additionalTestOutputDir /sdcard/Download/ com.example.benchmark/androidx.benchmark.junit4.AndroidBenchmarkRunner

在 Android 10(API 级别 29)及更高版本上,您的应用测试默认在存储沙盒中运行,这会阻止您的应用访问应用专用目录之外的文件。为了能够保存到全局目录(例如 /sdcard/Download),请传递以下检测参数

-e no-isolated-storage true

您还必须在基准测试的清单中明确允许旧版存储选项

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

如需了解更多信息,请参阅 暂时退出分区存储

检索文件

要从设备中检索生成的文件,请使用 adb pull 命令,它会将指定文件拉取到主机的当前目录中

adb pull /storage/emulated/0/Android/media/com.example.macrobenchmark/com.example.macrobenchmark-benchmarkData.json

要从指定文件夹中检索所有 benchmarkData,请查看以下代码段

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

跟踪文件(.trace.perfetto-trace)与 benchmarkData.json 保存在同一文件夹中,因此您可以以相同的方式收集它们。

基准测试数据示例

基准测试库会生成 JSON 文件,其中包含有关运行基准测试的设备以及所运行的实际基准测试的信息。以下代码段表示生成的 JSON 文件

{
    "context": {
        "build": {
            "brand": "google",
            "device": "blueline",
            "fingerprint": "google/blueline/blueline:12/SP1A.210812.015/7679548:user/release-keys",
            "model": "Pixel 3",
            "version": {
                "sdk": 31
            }
        },
        "cpuCoreCount": 8,
        "cpuLocked": false,
        "cpuMaxFreqHz": 2803200000,
        "memTotalBytes": 3753299968,
        "sustainedPerformanceModeEnabled": false
    },
    "benchmarks": [
        {
            "name": "startup",
            "params": {},
            "className": "com.example.macrobenchmark.startup.SampleStartupBenchmark",
            "totalRunTimeNs": 4975598256,
            "metrics": {
                "timeToInitialDisplayMs": {
                    "minimum": 347.881076,
                    "maximum": 347.881076,
                    "median": 347.881076,
                    "runs": [
                        347.881076
                    ]
                }
            },
            "sampledMetrics": {},
            "warmupIterations": 0,
            "repeatIterations": 3,
            "thermalThrottleSleepSeconds": 0
        }
    ]
}

其他资源