ARTICLE · 1288588
Reddit 启动快 51%,你的 App 未必:Macrobenchmark 如何验证 Baseline Profile
Reddit 启动快 51%,你的 App 未必:Macrobenchmark 如何验证 Baseline Profile
同一条启动基准,第一轮明显慢,后面几轮却顺了不少。看到这个差值,容易把功劳全算给刚改过的页面;也可能把 Baseline Profile 的收益算高了。因为启动变快的同时,ART 的编译状态、进程状态和缓存也可能变了。
结论先行,Baseline Profile 只能帮助 ART 更早优化它覆盖到的代码路径,不能保证每个 App 都快 51%。要判断自己的收益,得把关键用户旅程做成 profile,再用 Macrobenchmark 在受控条件下比较有、无 profile 的启动指标。
一、Profile 不是一份「已编译好的 App」
Baseline Profile 记录的是需要优先优化的类和方法规则,不是机器码,也不是某台手机完整的运行记录。应用把规则随包分发,ART 可以把它作为 profile-guided 编译的输入,对指定路径做提前编译;哪些代码最终生成了编译产物,仍受系统版本和编译策略影响。
不要把三种 profile 混成一件事。
两者最实际的区别是资料到达时机。Baseline Profile 可由构建产物随包分发,开发团队能在版本发布前准备;Cloud Profile 依赖真实用户数据聚合,需要等足够样本。Android 官方文档提到,Cloud Profile 聚合可能需要数小时到数天,频繁发布的 App 会有一段新版本尚未获得 Cloud Profile 的窗口。Baseline Profile 通常是 Startup Profile 规则的超集,除启动热路径外,也能覆盖启动后的关键旅程;但这不代表每种安装方式都在安装瞬间完成 AOT。
Android 代码通常以 DEX 字节码交付,ART 可以解释执行、在运行时用 JIT 编译热点,也可以通过 AOT 把部分工作前移。Baseline Profile 的价值,是在用户真正触碰某条路径之前,先告诉 ART 哪些类和方法值得优先处理。它不是把整款 App 预先编成机器码,也不携带每个用户的完整调用次数;哪些代码进入编译产物,仍受设备架构、系统版本和编译策略影响。
在 AOSP Android 17 的 ART 源码中,CompilerOptions 持有 ProfileCompilationInfo,ART Service 文档也把 dexopt 描述为 AOT 优化流程。这能证明 profile 是编译输入,不能证明其中每个方法都会无条件编译。Baseline Profile 最低支持 Android 7(API 24);Cloud Profile 的边界是 Android 9(API 28)。
关键用户旅程 | v生成 profile 规则 | v随 APK / AAB 分发 | vART 使用 profile 做 AOT +-- 覆盖路径,可提前优化 `-- 其他路径,按 ART 策略运行
图1:用户旅程生成的 profile 随应用分发,再由 ART 用于代码优化;未覆盖的路径不会因此自动变快。
Profile 也有完整生命周期。先选择真实的关键用户旅程,再生成规则、随版本打包、交给设备上的 ART 消费;App 更新后,旅程和代码可能变化,profile 也应随版本重新采集和验证。Reddit 的工程团队说,他们已为五条关键旅程生成 Baseline Profile,并在每次 release 自动重建。
二、51% 是一个案例,不是验收线
Reddit 的 51% 来自首个 Feed Baseline Profile 的早期 benchmark 中位启动时间改善,不能改写成所有 Reddit 页面、更不能改写成所有 Android App 都快 51%。这是 Android Developers Blog 在 2024 年公开转述的工程案例,不是本文独立复测。
这组数字有参考价值,因为它把收益落在具体页面和指标上;但公开文章没有完整测试条件,读者不能照着它复现出同一个 51%。拿它当「值得测」的信号可以,拿来设定所有团队的门槛不行。
更值得借鉴的是覆盖方式。打开首页、进入社区、登录、播放视频、聊天,不是同一条性能路径。Profile 生成测试只会记录它实际走过的类和方法;如果脚本在首帧就停,后续互动自然不会进入这份规则。覆盖要跟着重要用户旅程走,不是按源码文件数量铺满。
三、用 Macrobenchmark 做公平对照
对照实验要同时控制代码编译模式和启动模式。Macrobenchmark 把 CompilationMode 与 StartupMode 分开配置,前者决定测试采用什么编译条件,后者定义 App 以冷、温或热启动方式进入旅程。只把启动模式设成 COLD,不等于自动关闭 profile;如果两组的编译模式、安装包或测试路径不一致,结果就混入了其他变量。
timeToInitialDisplayMs | ||
timeToFullDisplayMs | reportFullyDrawn() 报告完整绘制 | |
FrameTimingMetric |
启动时间指标会输出最小值、中位数和最大值。Android 官方文档建议评估典型启动时关注中位数。TTID 回答用户何时看见第一帧,TTFD 则要等 App 通过 reportFullyDrawn() 宣告完整绘制;它们不是同一个终点。如果 App 报告得太早或太晚,TTFD 就会变成另一个问题的答案,因此两个对照组要用同一套「内容可用」定义。Compose 项目应按官方建议使用 Compose 兼容的绘制完成信号。timeToFullDisplayMs 在 Android 10(API 29)及更低版本可能不可用,缺失值不能当成零延迟。还要分清统计口径,StartupTimingMetric 的启动值是 min/median/max,不是 P95;滚动与动画帧的 P95/P99 应看 FrameTimingMetric。
同一设备 / 同一构建 / 同一旅程 | +------+------+ v vCompilationMode.None Partial + Profile | | +------+------+ v 比较 TTID / TTFD 中位数
图2:只改变编译模式,固定设备、构建和用户旅程,再比较相同启动指标。
可以照这个顺序做一轮可复现验证:
先选一条会被真实用户反复走的旅程。例如冷启动后到首页首屏可操作,不要只启动 Activity 就结束。把「可操作」定义清楚,否则每个团队测到的终点都不一样。 用 BaselineProfileRule采集这条旅程。官方流程通常把 Macrobenchmark 放在独立的com.android.test模块,以接近发布的构建运行生成测试。检查最终 APK/AAB 中确实有assets/dexopt/baseline.prof,也确认测试实际走过目标页面。准备两组 Macrobenchmark。无 profile 组用 CompilationMode.None();profile 组用CompilationMode.Partial(baselineProfileMode = BaselineProfileMode.Require),避免 profile 缺失时悄悄跑成另一种配置。两组都设为StartupMode.COLD。启动模式只固定进程怎样开始,是否使用 profile 由编译模式控制。锁住其他变量。同一台设备、同一构建、同一旅程,尽量保持电量、温度和后台负载相近。不要一组同时开 R8 full mode、另一组没有,也不要在 profile 组顺手换 Compose 版本,否则无法把差值归到 Baseline Profile。 重复运行并报告分布。固定 iterations并记录次数,保存StartupTimingMetric的最小值、中位数、最大值,以及需要时的帧时间百分位。若 TTID 改善而 TTFD 没动,差距可能发生在首帧之后的异步加载或绘制阶段;继续查 trace,别只报一个启动均值。
结果要和问题对应。TTID 下降,表示第一帧更早出现;TTID 变好而 TTFD 不动,说明首帧后的资源或渲染工作仍在拖延;启动时间不变但帧时间改善,则是滚动路径有变化,不能拿它替代启动结论。这样读数据,能避免把一个漂亮的均值误当成整个体验都变快。
图3:基准测试先设定编译条件,再冷启动目标 App,继而读取首帧、完整绘制与帧时间指标。
四、Profile 能解决什么,不能解决什么
Profile 优化的是被选中代码的执行路径,不是整个启动链路。如果慢点来自网络等待、磁盘 I/O、图片解码、过重布局或主线程锁竞争,单纯让方法更早编译,未必能碰到瓶颈。反过来,若启动热路径里有大量解释执行或 JIT 工作,Profile 才更可能带来明显变化。
还要留意两个边界。第一,profile 只覆盖采集时走过的路径,没覆盖的登录、深链或低频入口不能靠猜。第二,Baseline Profile 有包体积成本,Android 文档给出的文件上限为 1.5 MB;覆盖越多不等于越好,应该优先保留重要旅程。
第三,Profile 的生命周期不能停在生成那一步。把采集和 APK 包内文件检查纳入每次 release,再用同一条 Macrobenchmark 旅程做回归;代码路径大改后重新采集。API 24–27 支持 Baseline Profile,但 Cloud Profile 从 API 28 才开始;对较旧设备,timeToFullDisplayMs 还可能不可用,报告结果时应把 API 版本分开写。
如果 profile 组没有改善,先查最终包里有没有 profile、benchmark 是否用 Require、生成脚本是否走到当前版本的目标旅程。三项都对但数据仍不动,瓶颈可能不在可被编译优化的代码上;回到执行轨迹区分 CPU 计算与线程等待,再决定改哪一段。
今天可以先做一件小事:选一条最重要的冷启动旅程,明确首帧和完整绘制的定义,再跑一组无 profile 的 Macrobenchmark 基线。有了可靠基线,再生成 Profile、做相同条件的对照;如果没有改善,回到 trace 查实际等待点,而不是继续往 profile 里塞规则。
方法收束:先选对旅程,再生成 Profile,再用相同构建与 Macrobenchmark 对照;把中位数、长尾和未披露的条件一起报告。
公开来源
BaselineProfileRule | ||
源码参考
ProfileCompilationInfo | |||
ProfileCompilationInfo |
引用链接
[1]Baseline Profiles overview: https://developer.android.com/topic/performance/baselineprofiles/overview
[2]Create Baseline Profiles: https://developer.android.com/topic/performance/baselineprofiles/create-baselineprofile
[3]Measure Baseline Profiles: https://developer.android.com/topic/performance/baselineprofiles/measure-baselineprofile
[4]Capture Macrobenchmark metrics: https://developer.android.com/topic/performance/benchmarking/macrobenchmark-metrics
[5]Views 指标边界: https://developer.android.com/topic/performance/views/benchmarking/macrobenchmark-metrics-views
[6]ART Service README: https://android.googlesource.com/platform/art/+/android17-release/libartservice/service/README.md
[7]CompilerOptions: https://android.googlesource.com/platform/art/+/android17-release/compiler/driver/compiler_options.h
[8]ProfileCompilationInfo: https://android.googlesource.com/platform/art/+/android17-release/libprofile/profile/profile_compilation_info.h
[9]Reddit 使用 Baseline Profiles 与 R8 优化 Android 性能: https://android-developers.googleblog.com/2024/12/reddit-improved-app-startup-speed-using-baseline-profiles-r8.html
[10]ART Service README: https://android.googlesource.com/platform/art/+/android17-release/libartservice/service/README.md
[11]CompilerOptions: https://android.googlesource.com/platform/art/+/android17-release/compiler/driver/compiler_options.h
[12]ProfileCompilationInfo: https://android.googlesource.com/platform/art/+/android17-release/libprofile/profile/profile_compilation_info.h















































































