史诗级提升?Android Runtime 编译速度提高18%!

0 阅读4分钟

1. 概述

近日,Android官方宣布了一项引人瞩目的技术成果:Android Runtime(ART)的编译速度实现了18%的显著提升!

同时,还做到了:

  • 没有牺牲编译代码的质量,即对编译器最终输出质量没有负面影响
  • 没有出现内存回归,即提升过程中不增加内存占用峰值

2. 优化意义是什么?

即做了这次优化后,体现在哪些方面?

Android Runtime(ART)编译的速度不是【开发者用 Gradle 编译构建 APK 的速度】,而是【设备端 ART 对 字节码进行 AOT / JIT 编译的速度】。所以这次的提升效率是用户显而易见的:

  • 【更快的】应用冷启动、更【流程、少卡顿】:编译速度提升意味着应用(尤其是冷启动时)的代码能更快地被编译为高效机器码,从而减少启动等待时间和运行中的卡顿。
  • 【更快的】安装与更新流程:无论是新应用安装还是系统OTA更新,对字节码的编译处理环节都会更快,使得整个流程更加顺畅。
  • 低端设备【更友好】:编译效率的提升,降低了运行时编译的资源开销,这对硬件资源有限的低端设备尤其有益,有助于缩小不同设备间的体验差距。

3. 具体是怎么优化的?

这次的优化方向主要是:剔除低效代码,优化数据结构与算法。

优化1:剔除无效遍历,拒绝无用功

编译器内部有许多优化阶段(Pass),例如全局值编号(GVN)。官方发现,GVN中一个名为Kill的方法,无论实际情况如何,都会遍历所有节点进行检查,而大部分遍历是徒劳的。通过跳过已知无效的遍历,直接将此阶段的运行时间缩短了约15%,整体性能消耗从【1.023%降至约0.3%】

优化2:数据结构优化,从O(n)到O(1)

在LoadStoreAnalysis阶段,一个关键查找函数FindReferenceInfoOf原本采用线性搜索(O(n))。

优化后,将其数据结构改为以指令ID为索引的映射,实现了常数时间(O(1))查找,并预分配空间避免动态调整。仅此一项,就使该阶段加速34-66%,总编译时间提升【0.5-1.8%】。

优化3:内联检查前置,避免无效计算

编译器内联函数时,原本流程是先进行大量计算,最后才做资格检查。现在将如指令数检查等启发式规则前置,在计算前就过滤掉明显不符合条件的情况,避免了大量无效计算,带来了约【2%】的提升

优化4:适配现代使用模式,重构历史负担

代码库中遗留了一个为处理大型集合而优化的自定义HashSet。然而,当前的实际使用场景是创建大量小型、短生命周期的集合。通过调整实现以适应“小而短”的用法,减少了创建和销毁开销,编译时间提升【1.3-2%】,内存占用反而下降


4. 优化的同时带来了什么副作用?

在实际调整中,官方在落地这些调整时遇到了许多问题,因为当你修改了问题A后,往往容易引入更多的问题BCD。

但在最后结果中,实际上没有带来任何的【副作用】。在这里官方分享了其中几个比较关键问题,如:内存回归问题、历史遗留债务、方案过于复杂。

4.1 该重构就重构

在性能优化中,非常容易出现内存回归问题,即:提升过程中增加了内存占用峰值。

具体到这里的场景是:在优化“输出写入”阶段时,团队通过缓存计算值来加速(原本预计提升 1.3-2.8%),但自动化测试时发现,额外的缓存数据结构导致了内存使用量的显著增加

针对这个问题,官方并没有妥协,而是选择重构该阶段的底层逻辑,移除了冗余数据结构,最终不仅解决了内存问题,还获得了额外的速度提升:【0.5-1.8%】。

4.2 利用先进工具精准定位

团队广泛使用 pprof 进行深度分析,如生成 Flame Graph和 Bottom-up 视图,精准定位那些隐藏在代码深处的、“隐形”的性能开销,如频繁的对象拷贝。

4.3 “快速迭代”验证策略

为了高效验证优化思路,团队采用原型开发方式(Prototype),在典型应用(First-party apps, Android OS)上快速验证想法,确认收益后再进行完整的工程实现,节省了宝贵的开发时间。


总结

在复杂的历史代码中抽丝剥茧,用精准的手术刀取代粗暴的重锤

这是我对Android官方这次编译速度优化的评价,他们展示了教科书般的【既要又要】的性能优化最佳实践:我不仅要提升编译速度,同时要保持其他方面的体验与性能:如内存占用、稳定性等指标。


参考文档:android-developers.googleblog.com/2025/12/18-…