Android 宣布 Runtime 编译速度史诗级提升:在编译时间上优化了 18%

近期,Android 官方宣布了 Android Runtime 在编译时间上实现了 18% 的显著优化,同时不牺牲编译代码的质量,也没有增加峰值内存使用,换句话说,这属于是一个“速度提升 + 零损失”的优化成果。

实际上这个调整作为 2025 年的关键 KPI ,目前已经实现了分阶段 rollout:部分优化已经在 2025 年 6 月的 Android 发布中上线,其余会在年底发布中完成,所有运行 Android 12 及以上版本的用户都可以通过 mainline 更新得到这些改进。

“mainline 更新”( Project Mainline / Google Play System Updates) 是一种模块化更新机制,它支持 Google 通过 Google Play 直接更新 Android 系统的核心组件(如Android Runtime、媒体框架、安全模块等),这个更新不需要用户等待 OTA 。

而这次优化更加具体的说就是:

  • 编译时间减少了 18%
  • 对编译器最终输出质量没有负面影响
  • 没有出现内存回归(就是提升过程中不增加内存占用峰值)

并且,这些改进对于 JIT 和 AOT(预先编译) 都有用,当然这里提到的 Android Runtime(ART)“编译速度”不是你在本地用 Gradle 构建 APK 的速度,而是「设备端 ART 对 app 字节码进行 AOT / JIT 编译的速度」 ,一般而言针对ART 内部的“编译器执行速度”会有:

  • JIT 编译速度
  • AOT / dex2oat 编译速度
  • 编译器各个 IR / 优化 pass 的执行时间

例如在 ART 里有很多 pass(比如 GVN、某些数据流分析),每次都会遍历 IR,即使当前方法根本不满足优化条件也会完整跑一遍逻辑,这种行为带来的开销并没有产生收益:

官方提到,在之前有一个名为全局值编号 (GVN) 的阶段,这个过程里它有一个名为 Kill 的方法,这个方法会根据过滤器删除一些节点,由于它需要遍历所有节点并逐个检查,因此非常耗时,而事实上无论当时有多少节点存活,其实都能预先知道检查结果为 false ,那么在这种情况下完全可以跳过遍历,从而将性能消耗从 1.023% 降低到约 0.3%,并将 GVN 的运行时间缩短约 15%。

其他的一些案例,包括有:

FindReferenceInfoOf 的查找优化

LoadStoreAnalysis 阶段的方法 FindReferenceInfoOf 原本使用线性搜索 O(n) 在向量中查找,而现在将数据结构改为以指令 ID 为索引,实现 O(1) 查找,并预分配向量以避免 resize , 从而在这个阶段加速 34 - 66%,总编译时间提升 0.5-1.8%,虽然增加了一个计数字段,但峰值内存没有增加

结构调整

代码库中使用了一个自定义的 HashSet,多年前是为了处理极少数的大型集合而优化的,但现在的用法变成了创建大量小型的、短生命周期的集合,所以本地调整实现以适应“小而短”的用法,减少创建和销毁开销,从而让编译时间提升 1.3-2%,且内存使用量反而下降了 0.5-1%

还有通过将数据结构以引用方式传递给 lambda 表达式,避免了数据结构的复制,从而将编译时间缩短了约 0.5% 到 1% ,这一点在最初的代码审查中被忽略了,并在代码库中保留了多年:

内联 (Inlining) 检查

编译器为了性能会内联函数,原本的流程是先计算大量数据,最后再做“最终检查”(如指令数、寄存器需求)决定是否内联,而现在将这些检查从“计算后”移到了“计算前”作为启发式规则(Heuristics),避免了大量无效计算,仅指令数检查一项的移动就带来了约 2% 的提升

事实上官方在进行这些调整时也遇到了不少问题,因为即使你发现某个区域占用了大量编译时间,并且投入了开发时间尝试改进,有时也找不到解决方案,当你修改了 A 问题后,自然而然就带了 B 问题,比如:

  • 内存回归 :在优化“输出写入”阶段时,团队通过缓存计算值来加速(原本预计提升 1.3-2.8%),但自动化测试时发现,额外的缓存数据结构导致了内存使用量的显著增加
  • 历史遗留负担 :许多低效代码是因为历史原因遗留下来的(比如上述的 HashSet),或者是因为代码审查疏忽(将对象按值传递而不是按引用传递)
  • 复杂度的权衡 :某些优化方案可能过于复杂,或者会增加代码维护难度

针对这些问题,官方进行了一系列的调整尝试:

  • 重构解决内存回归 :针对“输出写入”阶段的内存问题,本次直接重构了该阶段的逻辑,这里设计了一种新方案,移除了两个冗余数据结构中的一个,这不仅解决了内存回退问题,还进一步提升了 0.5-1.8% 的速度
  • 使用 pprof 进行深度分析 :利用 pprof 工具生成 Flame Graph和 Bottom-up 视图,精准定位那些“隐形”的开销(如频繁的 Kill 方法或意外的对象拷贝)

Example of a profile’s flame graph in pprof
Example of a profile’s flame graph in pprof
Bottom up view of a profile
Bottom up view of a profile

  • “快速迭代”策略 :为了节省开发时间,先用原型(Prototype)在典型应用(First-party apps, Android OS)上快速验证想法,确认收益后再进行完整的工程实现和测试
  • 利用全新 C++ 特性 :比如使用 BitVectorView 替代可变长的 BitVector,并利用模板化实现让 Union() 操作在 64 位平台上一次处理两倍的位数

除此之外还有:

参考链接


macOS磁盘空间不释放(删除文件空间不恢复)

昨天(2025/12/15)把手上的 MacBook Pro M2 版本的系统升级到 macOS Tahoe 26.2 版本之后,发现系统剩余空间只有不到 50GB了,于是手工删除可一些不重要的文件,大约 200GB 的样子。

结果情况不仅没有好转,反倒是更严重了。更匪夷所思的是,文件删除的越多,系统剩余可用空间越少。最后急剧减少到接近为 0, 系统报错存储空间不足,内存不足。

怀疑是 TimeMachine 导致的问题,于是尝试了

也是不起任何作用,其中的一个镜像无论如何都删除不了。

最后发现是 Apple FS 的快照空间占用了这部分空间,很奇怪以前没有发生过类似情况。

怀疑是系统升级设置了回滚快照,防止升级出现问题。

但是系统升级完成后,快照逻辑没有被解除,导致文件越删除磁盘空间越少的诡异问题。

我这边操作删除的时候,这部分快照消耗了大约 400GB 的磁盘空间,删除快照之后,系统卡顿了大约几分钟的样子恢复正常。应该是系统在进行集中磁盘清理导致的锁盘引起卡顿。

  1. 打开"磁盘工具"
  2. 点工具栏的显示-显示APFS快照(或者按键盘command+shift+s)
  3. 下放会出现快照占用空间

参考链接


Mac OS 磁盘空间不释放(删除文件空间不恢复)

Flutter Uint8List to Pointer

参考链接


flutter Uint8List to Pointer<Uint8>

‘withUnsafeBytes’ is deprecated: use ‘withUnsafeBytes(_: (UnsafeRawBufferPointer) throws -> R) rethrows -> R’ instead

在编写 Swift 代码的时候,执行如下代码的时候:

出现如下警告:

修正警告的方式,参考使用如下代码:

参考链接


Flutter调试Linux平台代码(VSCode)

Flutter 开发过程中,需要编写调试 Linux 平台相关的代码,下面介绍一下使用 VSCode 进行调试的相关配置。

在工程根目录下的 launch.json 中增加如下配置:

注意 "program": "${workspaceFolder}/build/linux/arm64/debug/bundle/MyApp", // Path to your compiled Flutter Linux executable 根据项目的实际情况进行配置,主要需要修改的地方就是路径中的 arm64 或者 x64 以及最后的应用名。

完整的配置如下:

调试的时候,参考下图进行选择,选择的配置项目就是 "name": "Debug native",如下图:

参考链接


西门子洗碗机SN23E831TI排水泵故障维修

最近家里的西门子洗碗机不工作了,故障表现就是洗碗不结束,面板上的进水龙头指示灯闪烁,永远不结束。仔细观察,触摸洗碗机,会发现洗碗机外部凉凉的,不加热。洗碗中途打开柜门,里面没有温度。只要没有温度,大概率故障原因就是加热模块损坏。

继续阅读西门子洗碗机SN23E831TI排水泵故障维修

访问github.com被解析到127.0.0.1问题解决

最近访问 github,发现总是访问失败。经过排查后确认问题原因是 DNS 解析被污染导致 github.com 被解析到 127.0.0.1 导致的。

如下图:

解决方案:

1、指定DNS为其他知名DNS服务器:如1.1.1.1,8.8.8.8, 114.114.114.114等;

2、添加hosts文件记录,将github的ip地址增加进去。

参考链接


H3CBook Pro 14 G2合盖睡眠导致指纹解锁不可用M2硬盘丢失问题

公司新发的 H3CBook Pro 14 G2 使用了 Intel i7 1360P 当前 BIOS 版本号 F.11 版本日期 12/27/2024 。机器自带内存 16GB,感觉不大够用。到手之后,买了一个新的 32GB DDR5 内存,顺手插上了老电脑上替换下来的一个 Intel M2 固态硬盘。默认情况下合盖会进入自动睡眠状态。重新打开盖子,指纹解锁不可用,进入系统之后,新增的 M2 硬盘丢失。重启系统后可以恢复。

跟客服也沟通了一下,目前暂时没有新的 BIOS 版本发布。目前的解决方案就是在 Windows 11 的电源设置里面关闭合盖 “睡眠” 选项,全部设置为 “休眠”。

OpenGrok升级到1.14.4版本后在首页报错 "Method not found: class org.opengrok.web.PageConfig.getRelativePath"

OpenGrok升级到1.14.4版本后,在首页报错,如下:

继续阅读OpenGrok升级到1.14.4版本后在首页报错 "Method not found: class org.opengrok.web.PageConfig.getRelativePath"

API 版本迭代,怎么进行 API 多版本处理?

API 版本迭代是在软件开发过程中非常常见的一种需求,尤其对于 Web API 来说,随着业务需求的不断变化和技术的发展,API 的版本迭代变得愈发重要。API 版本迭代的目的是为了满足不断变化的业务需求、修复缺陷和改进功能,同时保持向后兼容性。然而,随着多个版本的 API 共存,如何进行多版本处理成为了必不可少的问题。

为什么需要进行版本迭代?
  1. 向后兼容性:在引入新功能或修改现有功能的同时,应该尽可能保持与现有版本的 API 的兼容,避免对已有客户端和应用程序的影响。
  2. 兼容性测试:为了确保新版本 API 的稳定性,需要进行兼容性测试。可以通过多版本处理来降低测试的成本和风险。
  3. 用户体验:通过多版本处理来提供更好的用户体验,向用户和开发者提供更多的选择和灵活性,使其能够逐步升级到新版本而不会影响其业务。
    因此,API 多版本处理对于确保系统的稳定性、提高开发效率和用户体验至关重要。
常见的 API 迭代模式

API 版本迭代需要进行版本控制,以便开发者和用户能清晰地了解各个版本之间的差异和兼容性情况。常见的 API 版本迭代模式包括语义版本控制(Semantic Versioning),它是一种广泛应用于软件开发领域的版本号命名规范。

语义版本控制将版本号分为主版本号、次版本号和修订号三个部分,分别代表了不同层次的变化:

  • 主版本号:当你做了不兼容的 API 修改时,需要升级主版本号。
  • 次版本号:当你做了向下兼容的功能性新增时,需要升级次版本号。
  • 修订号:当你做了向下兼容的问题修正时,需要升级修订号。
    语义版本控制的优点在于,通过版本号的规范化命名,能够清晰地表达版本之间的关系和变化。开发者和用户可以根据版本号了解这个版本带来了什么样的变化,以及对其现有应用的影响。这有助于提高版本变更的可预测性和透明度。

多版本处理的挑战

多版本处理是 API 设计中常见的问题之一。当 API 需要支持多个版本时,可能会遇到以下挑战:

  • 兼容性:不同版本的 API 可能存在不兼容的情况,导致客户端无法访问或使用所需版本的 API。
  • 功能冲突:不同版本的 API 可能包含不同的功能,导致客户端在使用不同版本的 API 时遇到功能冲突。
  • 对用户的影响:API 版本的变化可能会对用户的使用体验产生影响,例如用户需要升级客户端软件或重新学习如何使用 API。
  • 对开发者的影响:API 版本的变化可能会给开发者带来额外的开发和维护工作,例如开发者需要维护多个版本的 API 文档和代码库。
解决方案一:URI 版本控制

URI 版本控制是一种通过在 API 的 URI 中包含版本号来区分不同版本的 API 的方法。这种方法简单易懂,并且支持大部分客户端工具。

解决方案 URI 版本控制
概念 API 版本作为 URI 的一部分,如/api/v1/users表示第一个版本,/api/v2/users表示第二个版本。客户端通过在请求中指定 URI 版本号选择 API 版本。
优点
简单易懂:概念直观,易于理解。
  广泛支持:得到大部分客户端工具支持,包括浏览器、HTTP 客户端库和 RESTful API 框架。
  独立部署:不同 API 版本可独立部署,有利于开发和维护。
缺点 冗长:每个 URI 中包含版本号导致 URI 冗长。
  版本冲突:存在不同 API 版本时可能发生冲突,导致客户端无法访问所需版本。
  难以发现:URI 中包含版本号可能使客户端难以发现新 API 版本。
适用场景 API 版本较少且不经常更新。
  客户端工具支持 URI 版本控制。
  API 需要与多种客户端工具兼容。
解决方案二:标头版本控制

标头版本控制是一种通过在请求头中包含版本号来区分不同版本的 API 的方法。这种方法与 URI 版本控制相似,但更加灵活,并且可以支持更多类型的客户端工具。

解决方案 标头版本控制
概念 API 版本作为请求头的一部分,例如,使用Accept: application/json; version=2表示客户端请求使用 API 的第二个版本。服务器根据请求头中的版本号选择 API 版本。
优点 灵活:更加灵活,支持多种客户端工具,包括浏览器、HTTP 客户端库和 RESTful API 框架。
  版本独立:与 URI 无关,不同 API 版本可以在同一个 URI 上部署。
  易于发现:通过请求头可以发现新的 API 版本。
缺点 复杂性:概念较复杂,可能需要客户端工具特殊处理。
  兼容性:需要客户端工具支持,否则无法使用。
  性能:可能影响性能,因为服务器需要额外处理请求头。
适用场景 API 版本较多且经常更新。
 
客户端工具支持标头版本控制。
  API 需要与多种类型的客户端工具兼容。
解决方案三:内容协商

内容协商是一种根据客户端的需求动态选择合适的 API 版本的机制。这种机制允许客户端在请求中指定它支持的 API 版本,然后服务器根据客户端支持的版本选择要使用的 API 版本。

解决方案 内容协商
概念 根据客户端需求动态选择合适的 API 版本,通过 HTTP 协议中的Accept和Content-Type请求头指定客户端支持的版本和内容类型。服务器根据请求头信息选择 API 版本和内容类型。
优点 动态选择:允许客户端动态选择合适的 API 版本,更容易适应客户端需求。
  兼容性:允许客户端使用不同的 API 版本,提高 API 兼容性。
  性能:提高性能,服务器可以根据客户端需求选择最合适的 API 版本和内容类型。
缺点 复杂性:概念较复杂,需要客户端工具和服务器特殊处理。
  兼容性:需要客户端工具和服务器都支持内容协商,否则无法使用。
适用场景 API 版本较多且经常更新。
  客户端工具和服务器都支持内容协商。
  API 需要与多种类型的客户端工具兼容。
通过 Apifox 进行版本控制

Apifox 是一个集接口文档、API 调试、自动化测试、Mock 服务等功能于一体的综合 API 开发协作工具。Apifox = Postman + Swagger + Mock + JMeter,Apifox 支持调试 http(s)、WebSocket、Socket、gRPC、Dubbo 等协议的接口,并且集成了 IDEA 插件

原始链接


API 版本迭代,怎么进行 API 多版本处理?