持续集成中的基准测试

您可以在持续集成 (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 作为 instrumentation runner。

您可以使用 -e 参数传递与 Gradle 配置中相同的 instrumentation 参数。 有关所有 instrumentation 参数选项,请参阅 Microbenchmark Instrumentation 参数添加 instrumentation 参数 以获取 Macrobenchmark。

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

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

有关如何从命令行运行 instrumentation 测试的更多信息,请参阅 使用 ADB 运行测试.

锁定时钟

Microbenchmark Gradle 插件提供命令 ./gradlew lockClocks 用于锁定已植根设备的 CPU 时钟。 当您可以访问已植根设备(例如“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 命令运行,您需要手动提取文件。 默认情况下,报告保存在测试应用程序外部存储的媒体目录中的设备上。 为方便起见,库将文件的路径打印到 Logcat 中。 请注意,输出文件夹可能因基准测试运行的 Android 版本而异。

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

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

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

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

-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
        }
    ]
}

其他资源