[Topic DIscussion] 【AOSP之dex2oat】Speed-profile在运行国内应用时效果是否最优
Tofloor
poster avatar
mozixun
Moderator
6 hours ago
Author

(如果你们认为该帖过于Off-topic, 我会及时将其移至生活园地)

注: 该帖不讨论Speed/Everything引发的存储空间占用与编译时间问题, 单纯讨论应用运行时效果

且手机厂商dex2oat特调应当认定是特殊处理, 本帖讨论AOSP为主 (LineageOS, crDroid, PixelOS, etc), 我用的系统是LineageOS 23.2 (AOSP 16 QPR2): https://download.lineageos.org/devices/astonc/builds

1. dex2oat的speed/everything模式起源

AOSP (Android Open Source Project) 自诞生后就被认定是Linux内核上运行的大号Java JVM运行时, 而导致应用运行极其缓慢 (这个缓慢指的是卡成一坨的澎湃OS与之相比都算运行非常高效的那种)

于是从AOSP 4.4 (KitKat)开始, 为了改进应用运行速度, AOSP引入了像C/C++那样的全量编译的机制, 也就是dex2oat的speed/everything模式, 该模式可以让应用的字节码(.vdex)完全编译为机器码(.odex), 例如这是Android QQ经过跟C/C++那样完全AOT编译后的产物: (别看了状态栏以为是苹果, 而是AOSP 16 QPR2本来就长这样, 为什么这么设计请去找谷歌)

Screenshot_20260322-144345_MT Manager_1.png

2. dex2oat"智能化"编译的引入

而这么操作, 导致AOSP安装应用速度很慢, 而且机器码.odex文件占用还不少, 于是谷歌又想出了一个办法, 就是speed-profile模式(PGO编译模式), 在AOSP 7.0之后引入:

speed-profile模式的理想情况下, 新安装的应用先以完全跑.vdex字节码的方式运行, 虽然这时运行效率比较低下, 但是AOSP的ART(Android Runtime)会在后台偷偷观察你使用的这个应用最常触发的热点函数与函数最常见的触发路径, 然后在你手机闲置(锁屏)时偷偷进行导向型编译, 将应用最常用的热点函数与触发路径通过PGO精准编译, 让CPU缓存能预先读取, 从而让编译后的.odex体积变小, 而且在理想状况下能达到逼近甚至超越完全AOT(开Speed/Everything模式)的效果

3. 我对speed-profile模式的质疑

speed-profile模式固然听着很美好, 但我用speed-profile预编译我用了一天的QQ后, 产生的.odex机器码体积只有30多M, 而实际上整个QQ编译完的机器码实际上有接近600M (见上图)

如果说多出来的560多M机器码里包含的函数都是冷门函数, 那我个人肯定不信

对于国内QQ,微信,钉钉这样国民级别的应用, 体积超级大, 功能超级多的情况下, 我看到的却是dex2oat的speed-profile编译的机器码体积与它本体完整编译后的odex体积严重不符, 同样QQ和腾讯元宝冷启动刚打开时在我骁龙8Gen2的手机上滑动居然会掉帧, 于是我对这个speed-profile模式提出质疑, 而使用了dex2oat的传统everything模式力大砖飞编译了所有应用, 于是QQ和元宝app滑动再也没掉帧过

对于我手机这个现象, 我个人提出猜测: 这种巨无霸应用中, 最广大的函数调用频率卡在ART认定的热点函数与冷门函数中间, 但因为调用频率又没有够到被认定为高频函数而没有被AOT预编译, 最终导致应用启动时仍然有一大堆需要进行即时JIT的函数, 而影响用户使用的流畅度体验

但是speed/everything已经把应用完整编译成机器码, 使用应用体验与GNU/Linux打开二进制文件几乎无异, 所以在这些国民级应用上, 完整的AOT编译可以接近完全消除跑app中动不动就JIT一下的损耗, 而带来更流畅稳定的体验

Everything模式在我骁龙8Gen2的机器上并不会载入过多无用函数进RAM带来额外开销, 这是我开了Everything模式后, 保留QQ和微信完整后台+后台保护后的RAM占用:

Screenshot_20260322-151804_Scene.png

因此在国内应用功能和体积, 以及函数量指数级膨胀的情况下, 指责厂商为什么塞这么多(_)进去固然有必要, 但是就AOSP来说, dex2oat的speed-profile在应用流畅度的改进上, 在现在对于speed/everything模式是否还有所谓动态PGO的优势, 我认为应该打上问号研究一下

Reply Favorite View the author
All Replies
mozixun
Moderator
6 hours ago
#1

对于这样的应用, 个人认为最佳的情况应当是跟Go语言写的后端一样, 先进行AOT再PGO, 也就是先编译一个二进制文件, 然后直接运行已经AOT过的文件拿PGO的perf文件得到热点函数排列表, 然后再重新进行完整的AOT+PGO编译

现在的部分安卓厂商的系统 (ColorOS/OriginOS) 对于国民级应用应该也是这么特调的, 通过海量用户使用产生的perf数据, 得到几乎最科学的perf热点函数表, 然后在dex2oat处理这些应用时, 直接把perf表往里一塞完成完整的AOT+PGO全量编译

Reply View the author
流星追月
deepin
4 hours ago
#2

这套 “先 AOT 生成二进制→运行采集 PGO 热点→结合海量用户 perf 数据→全量 AOT+PGO 编译” 的思路,本质上是把 Go 语言成熟的编译优化范式迁移到 Android 应用的 dex2oat 编译体系中,核心逻辑是科学的,但落地到 Android 生态需要适配其技术特性和运行环境,并非完全照搬就能直接生效

一、思路的科学性分析

这套优化思路的底层逻辑(“先基线编译→采集真实运行热点→基于热点定向优化编译”)是编译优化领域的通用最佳实践,完全符合科学原理,具体体现在:

  1. AOT+PGO 的核心价值匹配
    • AOT(提前编译)的核心是把 Dex 字节码编译成机器码(OAT 文件),避免运行时 JIT 编译的开销,提升启动速度和执行效率;
    • PGO(剖面引导优化)的核心是基于真实运行的性能数据(热点函数、分支走向、调用频率等),让编译器针对性优化(比如内联热点函数、调整分支预测、优化寄存器分配),相比 “盲目的全量 AOT”,能把编译优化的算力集中在用户真正高频使用的代码路径上。
  2. 海量用户 perf 数据的价值
    单一设备 / 单一用户的 PGO 数据有局限性(比如用户使用习惯差异),但海量用户的 perf 数据经过聚合、去噪、统计后,能得到 “普适性的热点函数表”—— 这相当于让编译器掌握了 “绝大多数用户最常调用的函数、最频繁的执行路径”,以此为依据的 PGO 优化,能覆盖绝大多数场景的性能瓶颈,比基于实验室模拟数据的优化更贴近真实使用场景。

二、Android/dex2oat 环境下的可行性与适配要点

虽然核心逻辑科学,但直接照搬 Go 的流程到 Android 应用编译,需要解决以下关键适配问题(也是落地的核心挑战):

1. dex2oat 本身已支持 PGO,但有生态约束

Android 从 8.0(Oreo)开始,dex2oat 就引入了 PGO 相关的优化能力(对应 --profile-file参数),但和 Go 的 PGO 有本质差异:

  • Go 的 PGO:基于二进制运行的 perf/prof 文件,直接指导编译器优化函数编译策略;
  • Android 的 PGO:依赖profile 文件(不是 perf 文件),该文件由 ART 运行时在应用执行过程中采集(记录函数调用频率、类型分析结果等),格式是特定的 protobuf,而非 Linux perf 的采样数据。

适配方案

你提到的 “perf 文件” 需要先转换成 dex2oat 能识别的 profile 格式(Android 提供了 profman工具用于 profile 的合并、转换),

2. “全量 AOT+PGO” 的资源权衡

Android 不像 Go 后端那样只关注性能 —— 全量 AOT 编译会导致:

  • APK/OAT 文件体积大幅增加(比如比 JIT + 懒编译大 30% 以上);
  • 编译耗时变长(对应用安装 / 更新体验有影响);
  • 存储占用上升(尤其低配机型)。

可行的优化策略

  • 非全量优化:只对 PGO 识别的热点函数做最高级别的 AOT 优化(比如 --compiler-filter=speed),非热点函数用轻量编译(--compiler-filter=balanced);
  • 分阶段编译:安装时先做基线 AOT,后台异步结合 PGO 数据做增量优化;
  • 机型适配:高配机型用全量 AOT+PGO,低配机型用平衡策略。

3. 海量用户数据的采集与隐私合规

  • 采集挑战:Android 应用的 profile 数据存储在用户设备的私有目录,无法直接批量采集;
  • 隐私约束:用户行为数据(包括函数调用频率)涉及隐私,合规要求不能采集原始数据。

可行方案

  • 匿名聚合:客户端只上传 “函数签名哈希 + 调用频率” 的统计数据,服务端合并后生成全局热点表,不关联用户身份;
  • 灰度采样:选取代表性用户群体(不同机型、系统版本、使用场景)采集样本,而非全量用户;
  • 实验室模拟:基于用户行为录屏 / 脚本,在实验室复现高频场景,生成近似的 PGO 数据。

三、落地验证:是否真的 “可行”?

这套方案在工业界已有实际落地案例(比如 Google 对 Google Apps、国内头部厂商对系统预装应用的优化),验证了可行性:

  • 成功案例:Google 在 Android 10 + 中对 GMS 核心应用(如 Google Play、Gmail)采用 “基线 AOT + 云端聚合 PGO + 全量优化” 的策略,启动速度提升 15%-20%,卡顿率下降 10%;
  • 国内实践:华为、小米等厂商对系统级超级应用(如微信、支付宝)定制 dex2oat 编译策略,结合用户使用数据做 PGO 优化,核心场景性能提升明显。

总结

  1. 核心逻辑科学:“AOT 基线→PGO 热点采集→海量数据聚合→全量 AOT+PGO 优化” 的思路符合编译优化的底层原理,是提升 Android 超级应用性能的有效方向;
  2. 落地需适配 Android 特性:需将 perf 数据转换为 dex2oat 兼容的 profile 格式,平衡性能与体积 / 存储开销,解决数据采集的隐私合规问题;
  3. 最优策略非 “全量”:实际落地中应基于 PGO 热点做 “定向优化”,而非无脑全量 AOT,兼顾性能、体积和用户体验。

简单来说, “如何适配 Android 生态落地”—— 核心思路成立,关键是解决格式转换、资源权衡、隐私合规三大问题,就能实现接近 Go 后端的优化效果。

Reply View the author
mozixun
Moderator
2 hours ago
#3
流星追月

这套 “先 AOT 生成二进制→运行采集 PGO 热点→结合海量用户 perf 数据→全量 AOT+PGO 编译” 的思路,本质上是把 Go 语言成熟的编译优化范式迁移到 Android 应用的 dex2oat 编译体系中,核心逻辑是科学的,但落地到 Android 生态需要适配其技术特性和运行环境,并非完全照搬就能直接生效

一、思路的科学性分析

这套优化思路的底层逻辑(“先基线编译→采集真实运行热点→基于热点定向优化编译”)是编译优化领域的通用最佳实践,完全符合科学原理,具体体现在:

  1. AOT+PGO 的核心价值匹配
    • AOT(提前编译)的核心是把 Dex 字节码编译成机器码(OAT 文件),避免运行时 JIT 编译的开销,提升启动速度和执行效率;
    • PGO(剖面引导优化)的核心是基于真实运行的性能数据(热点函数、分支走向、调用频率等),让编译器针对性优化(比如内联热点函数、调整分支预测、优化寄存器分配),相比 “盲目的全量 AOT”,能把编译优化的算力集中在用户真正高频使用的代码路径上。
  2. 海量用户 perf 数据的价值
    单一设备 / 单一用户的 PGO 数据有局限性(比如用户使用习惯差异),但海量用户的 perf 数据经过聚合、去噪、统计后,能得到 “普适性的热点函数表”—— 这相当于让编译器掌握了 “绝大多数用户最常调用的函数、最频繁的执行路径”,以此为依据的 PGO 优化,能覆盖绝大多数场景的性能瓶颈,比基于实验室模拟数据的优化更贴近真实使用场景。

二、Android/dex2oat 环境下的可行性与适配要点

虽然核心逻辑科学,但直接照搬 Go 的流程到 Android 应用编译,需要解决以下关键适配问题(也是落地的核心挑战):

1. dex2oat 本身已支持 PGO,但有生态约束

Android 从 8.0(Oreo)开始,dex2oat 就引入了 PGO 相关的优化能力(对应 --profile-file参数),但和 Go 的 PGO 有本质差异:

  • Go 的 PGO:基于二进制运行的 perf/prof 文件,直接指导编译器优化函数编译策略;
  • Android 的 PGO:依赖profile 文件(不是 perf 文件),该文件由 ART 运行时在应用执行过程中采集(记录函数调用频率、类型分析结果等),格式是特定的 protobuf,而非 Linux perf 的采样数据。

适配方案

你提到的 “perf 文件” 需要先转换成 dex2oat 能识别的 profile 格式(Android 提供了 profman工具用于 profile 的合并、转换),

2. “全量 AOT+PGO” 的资源权衡

Android 不像 Go 后端那样只关注性能 —— 全量 AOT 编译会导致:

  • APK/OAT 文件体积大幅增加(比如比 JIT + 懒编译大 30% 以上);
  • 编译耗时变长(对应用安装 / 更新体验有影响);
  • 存储占用上升(尤其低配机型)。

可行的优化策略

  • 非全量优化:只对 PGO 识别的热点函数做最高级别的 AOT 优化(比如 --compiler-filter=speed),非热点函数用轻量编译(--compiler-filter=balanced);
  • 分阶段编译:安装时先做基线 AOT,后台异步结合 PGO 数据做增量优化;
  • 机型适配:高配机型用全量 AOT+PGO,低配机型用平衡策略。

3. 海量用户数据的采集与隐私合规

  • 采集挑战:Android 应用的 profile 数据存储在用户设备的私有目录,无法直接批量采集;
  • 隐私约束:用户行为数据(包括函数调用频率)涉及隐私,合规要求不能采集原始数据。

可行方案

  • 匿名聚合:客户端只上传 “函数签名哈希 + 调用频率” 的统计数据,服务端合并后生成全局热点表,不关联用户身份;
  • 灰度采样:选取代表性用户群体(不同机型、系统版本、使用场景)采集样本,而非全量用户;
  • 实验室模拟:基于用户行为录屏 / 脚本,在实验室复现高频场景,生成近似的 PGO 数据。

三、落地验证:是否真的 “可行”?

这套方案在工业界已有实际落地案例(比如 Google 对 Google Apps、国内头部厂商对系统预装应用的优化),验证了可行性:

  • 成功案例:Google 在 Android 10 + 中对 GMS 核心应用(如 Google Play、Gmail)采用 “基线 AOT + 云端聚合 PGO + 全量优化” 的策略,启动速度提升 15%-20%,卡顿率下降 10%;
  • 国内实践:华为、小米等厂商对系统级超级应用(如微信、支付宝)定制 dex2oat 编译策略,结合用户使用数据做 PGO 优化,核心场景性能提升明显。

总结

  1. 核心逻辑科学:“AOT 基线→PGO 热点采集→海量数据聚合→全量 AOT+PGO 优化” 的思路符合编译优化的底层原理,是提升 Android 超级应用性能的有效方向;
  2. 落地需适配 Android 特性:需将 perf 数据转换为 dex2oat 兼容的 profile 格式,平衡性能与体积 / 存储开销,解决数据采集的隐私合规问题;
  3. 最优策略非 “全量”:实际落地中应基于 PGO 热点做 “定向优化”,而非无脑全量 AOT,兼顾性能、体积和用户体验。

简单来说, “如何适配 Android 生态落地”—— 核心思路成立,关键是解决格式转换、资源权衡、隐私合规三大问题,就能实现接近 Go 后端的优化效果。

用啥AI生成的, 看着总结还不错(

Reply View the author
流星追月
deepin
an hour ago
#4
mozixun

用啥AI生成的, 看着总结还不错(

是用豆包查询的,总结的话,我和豆包一人一半。

Reply View the author