Jetpack Benchmark 库的 Macrobenchmark 稳定性,CI 环境下跑得过吗

Jetpack Benchmark 库的 Macrobenchmark 稳定性,CI 环境下跑得过吗

Jetpack Benchmark 库的 Macrobenchmark 稳定性,CI 环境下跑得过吗


Jetpack Benchmark 库的 Macrobenchmark 稳定性,CI 环境下跑得过吗


Macrobenchmark 这个库从 AndroidX Benchmark 1.1.0 被扶正以来,Google 官方文档的口径就一直是“把性能测试左移”、“在 CI 中建立性能基准线”。但凡是真动手把 measureRepeated 块塞到 MacrobenchmarkRule 里、然后试图让 GitHub Actions 每晚跑一遍的开发者,大概率都碰过同一种绝望:同一套 startup benchmark,本地 Pixel 6 连着跑三轮,启动时间方差控制在 8% 以内;一模一样的二进制丢到 Firebase Test Lab 的物理设备队列里,方差能飙到 35% 甚至 50%。这时候你盯着 Grafana 上那条像心电图一样的折线,根本分不清是昨天那笔 Kotlin 升级搞垮了性能,还是云手机刚从上一个游戏团队的烫手测试里缓过来。Macrobenchmark 不是不能跑 CI,是它的整个设计前提跟云端共享设备、未 root、温控严苛的环境天生相克。Google 生态团队自己在 I/O 上演示用的都是 rooted Pixel 实验室集群,却把“建议锁频”这句话轻描淡写地埋在文档第十页,这多少有点何不食肉糜。


`lockClocks` 的门槛:CI 设备连入场券都没有


Macrobenchmark 底层对测量稳定性的假设非常硬。它期望设备在测试期间 CPU/GPU 频率恒定、不触发 thermal throttling、后台没有流氓进程抢占调度器。为了达到这个理想状态,库内部会尝试通过 adb shell cmd activity memory-factor critical 或者 root 权限下的 lockClocks 脚本来钉死频率。Jetpack Benchmark 1.2.0 的官方文档里白纸黑字写着:“最佳实践是使用已 root 的设备进行基准测试。” 这句话翻译成现实就是:如果你的 CI 跑在 Firebase Test Lab、AWS Device Farm 或者 GitHub Actions 提供的共享 macOS runner 上,你连门票都没买到。


未 root 的设备上,Macrobenchmark 的 fallback 策略可怜得很。它只能调低屏幕亮度、关掉动画、尽量推应用进缓存,然后祈祷系统调度器给点面子。Android 12(API 31)之后,Google 进一步收紧了 WRITE_SECURE_SETTINGS 和 root shell 命令的可用性,很多云设备厂商连 adb shell settings put global sys_vm_stats 0 这类操作都要弹安全警告。后果就是,CI 上跑出来的 trace 文件里经常能看到 CPU 频率在测试中途从 1.8GHz 跳到 1.2GHz,而 MacrobenchmarkScope 提供的 isThermalThrottled 检查在未 root 设备上根本就是盲猜——它读不到 /sys/class/thermal/thermal_zone*/temp,只能依赖系统上报的 POWER_THROTTLING 信号,后者在部分 OEM 定制 ROM 上根本不存在。你拿到一份“启动耗时 420ms”的报告,其实里头有 80ms 是 CPU 降频等来的,这数据你敢当 regression gate 用?


`CompilationMode` 在云端的时间黑洞


Macrobenchmark 默认会操纵应用的 AOT 编译状态。你写 measureRepeated 测冷启动,库底层会拿着 pm compile -f speed-profile 或者 --full 去折腾 target package。这个设计在本地很好理解:确保每次测试都是相同的 ART 编译状态,排除 profile-guided compilation 的干扰。但在 CI 里,它就是个不定时炸弹。


Firebase Test Lab 的物理设备实例是复用的,虽然每次测试会清数据,但 pm compile 触发的 dex2oat 进程在后台烧 CPU 的时间完全不可控。曾经有个很典型的场景:某个团队在 FTL 上跑 CompilationMode.Full() 的 startup benchmark,本地 40 秒能结束的测试,在 FTL arm64 队列里硬生生拖了 6 分半。不是因为 app 变重了,而是 FTL 那台共享设备当时的 I/O 调度被隔壁进程的 dex2oat 占满,Macrobenchmark 的 compilation phase 在等待 ART 完成全量编译。更坑的是,Gradle Managed Device(GMD)的默认 timeoutSeconds 在 AGP 8.2 里对这类测试并不友好,benchmark 进程被 system server 杀掉的日志在 FTL 上又看不着,你只能看到 JUnit runner 抛一个模糊的 ProcessCrashException,然后 CI 标注失败

Google Play 的政策收紧,开发者账号被封怎么办 2026-09-11
WorkManager 的约束条件,电池优化下还靠谱吗 2026-09-11

评论区