夜雨聆风学习资料网

ARTICLE · 1288588

Reddit 启动快 51%,你的 App 未必:Macrobenchmark 如何验证 Baseline Profile

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
开发者用关键用户旅程生成,随 APK/AAB 分发
让 ART 提前看到希望优化的代码路径
只覆盖采集到的路径,不是全 App 的性能承诺
Cloud Profile
Google Play 汇总用户设备上的运行信息
给后续安装提供群体使用热点
新版本刚上线时,汇总数据可能尚未形成
运行时 Profile
单台设备上 App 实际运行时积累
支持 ART 后续按运行情况继续优化
不能替代首次安装前就知道的关键路径

两者最实际的区别是资料到达时机。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 年公开转述的工程案例,不是本文独立复测。

Reddit 公开结果
原文给出的范围
不能据此推断什么
首个 Feed Baseline Profile
早期 benchmark 的中位启动时间改善 51%
原文未披露设备、Android 版本、重复次数和基线绝对时间
Home Feed profile
冻结帧 P95 减少 36%
不能把该数值套到启动以外的所有页面
Community Feed profile
P90 TTI 改善 12%,首次绘制时间减少 22%
不是所有用户旅程的统一收益
全 App 综合结果
冷启动改善 20%、滚动改善 15%
该部分同时包含 Baseline Profiles、R8 等优化,不能单独归因给 profile

这组数字有参考价值,因为它把收益落在具体页面和指标上;但公开文章没有完整测试条件,读者不能照着它复现出同一个 51%。拿它当「值得测」的信号可以,拿来设定所有团队的门槛不行。

更值得借鉴的是覆盖方式。打开首页、进入社区、登录、播放视频、聊天,不是同一条性能路径。Profile 生成测试只会记录它实际走过的类和方法;如果脚本在首帧就停,后续互动自然不会进入这份规则。覆盖要跟着重要用户旅程走,不是按源码文件数量铺满。

三、用 Macrobenchmark 做公平对照

对照实验要同时控制代码编译模式和启动模式。Macrobenchmark 把 CompilationMode 与 StartupMode 分开配置,前者决定测试采用什么编译条件,后者定义 App 以冷、温或热启动方式进入旅程。只把启动模式设成 COLD,不等于自动关闭 profile;如果两组的编译模式、安装包或测试路径不一致,结果就混入了其他变量。

测量项
Macrobenchmark 的含义
适合回答的问题
timeToInitialDisplayMs
系统收到启动 intent 到 Activity 第一帧显示
用户多久看见首屏
timeToFullDisplayMs
到 App 通过 reportFullyDrawn() 报告完整绘制
首屏何时达到 App 定义的可用状态
FrameTimingMetric
采样帧耗时,可看 P50/P90/P95/P99
profile 是否改善滚动或动画卡顿

启动时间指标会输出最小值、中位数和最大值。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:只改变编译模式,固定设备、构建和用户旅程,再比较相同启动指标。

可以照这个顺序做一轮可复现验证:

  1. 先选一条会被真实用户反复走的旅程。例如冷启动后到首页首屏可操作,不要只启动 Activity 就结束。把「可操作」定义清楚,否则每个团队测到的终点都不一样。
  2. 用 BaselineProfileRule 采集这条旅程。官方流程通常把 Macrobenchmark 放在独立的 com.android.test 模块,以接近发布的构建运行生成测试。检查最终 APK/AAB 中确实有 assets/dexopt/baseline.prof,也确认测试实际走过目标页面。
  3. 准备两组 Macrobenchmark。无 profile 组用 CompilationMode.None();profile 组用 CompilationMode.Partial(baselineProfileMode = BaselineProfileMode.Require),避免 profile 缺失时悄悄跑成另一种配置。两组都设为 StartupMode.COLD。启动模式只固定进程怎样开始,是否使用 profile 由编译模式控制。
  4. 锁住其他变量。同一台设备、同一构建、同一旅程,尽量保持电量、温度和后台负载相近。不要一组同时开 R8 full mode、另一组没有,也不要在 profile 组顺手换 Compose 版本,否则无法把差值归到 Baseline Profile。
  5. 重复运行并报告分布。固定 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 对照;把中位数、长尾和未披露的条件一起报告。

公开来源

类别
原始公开资料
本文使用范围
Android 官方文档
Baseline Profiles overview[1]
Profile 文件、API 边界、Startup Profile 关系与包体积限制
Android 官方文档
Create Baseline Profiles[2]
BaselineProfileRule
 与关键用户旅程的生成流程
Android 官方文档
Measure Baseline Profiles[3]
profile/no-profile 对照方法与编译模式
Android 官方文档
Capture Macrobenchmark metrics[4]、Views 指标边界[5]
TTID、TTFD、帧时间及统计值;旧版 Android 的 TTFD 可用性
AOSP Android 17 源码
ART Service README[6]、CompilerOptions[7]、ProfileCompilationInfo[8]
dexopt/AOT 与 profile compilation input 的上游实现边界
工程案例
Reddit 使用 Baseline Profiles 与 R8 优化 Android 性能[9](2024-12-12)
首个 Feed profile 的早期 51% 中位启动改善及其披露边界

源码参考

类别
机制 / 符号
参考源码或文档
用途
AOSP Android 17
dexopt / AOT
ART Service README[10]
核对 dexopt 的定义与 AOT 优化流程
AOSP Android 17
ProfileCompilationInfo
 编译选项
CompilerOptions[11]
核对 profile-guided compilation input 的字段
AOSP Android 17
ProfileCompilationInfo
 类型
ProfileCompilationInfo[12]
核对 Profile 编译输入的数据结构与适用边界

引用链接

[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

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料