持续集成环境中的基准测试

您可以在持续集成 (CI) 环境中运行基准测试来跟踪性能,在应用发布之前就能识别出性能是否有所下降或提升。本页介绍了有关在 CI 环境中进行基准测试的基本信息。

开始在 CI 环境中进行基准测试之前,请考虑捕获和评估结果会在哪些方面不同于常规测试。

结果模糊

虽然基准测试属于插桩测试,但提供的结果不是简单的“通过”或“失败”。基准测试会针对运行基准测试的指定设备提供时间测量结果。通过将一段时间内的结果绘制成图表,使您能够在测量系统中监控变化情况和观察噪声。

使用真实设备

在实体 Android 设备上运行基准测试。虽然基准测试可在模拟器上运行,但强烈建议您不要这样做,因为产生的结果并不代表真实的用户体验,只不过是提供了与主机 OS 和硬件功能相关联的测量值而已。请考虑使用真实设备或者可让您在真实设备上运行测试的服务(例如 Firebase Test Lab)。

运行基准测试

在 CI 流水线中运行基准测试与从 Android Studio 在本地运行基准测试不同。如果在本地运行,您通常需要通过一个 Gradle connectedCheck 任务运行 Android 集成测试。此任务会自动构建和测试 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 插桩命令以运行所有基准测试:

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

使用 Macrobenchmark 库时,请将常规 androidx.test.runner.AndroidJUnitRunner 用作插桩运行程序。

您可以使用 -e 参数传递与 Gradle 配置中相同的插桩参数。如需了解所有插桩参数选项,请参阅 Microbenchmark 插桩参数或为 Macrobenchmark 添加插桩参数

例如,您可以设置 dryRunMode 参数以在拉取请求验证流程中运行 Microbenchmark。启用此标志后,Microbenchmark 便只会在单次循环中运行,这样可以验证 Microbenchmark 是否在正常运行,而又不会占用太长的执行时间。

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

如需详细了解如何从命令行运行插桩测试,请参阅使用 adb 运行测试

锁定时钟

Microbenchmark Gradle 插件提供 ./gradlew lockClocks 命令,用于在已取得 root 权限的设备上锁定 CPU 时钟。如果您有已取得 root 权限的设备(例如“userdebug”build),这对确保稳定性非常有用。您可以使用库的源代码中提供的 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 命令直接运行,则需要手动拉取这些文件。默认情况下,报告会保存到被测应用在设备上外部存储空间的媒体目录中。为方便起见,该库会将文件路径输出到 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
        }
    ]
}

其他资源