Firefox 编译提速 17%,功劳不在编译器:Mozilla 盯上了那段总被忽视的 Python 代码

Firefox 这次提速,改的不是“编译器”,而是编译前那堆没人爱看的活
开源世界里,性能优化常常分两类。一类像跑车换发动机,人人都能看懂;另一类像把仓库里的货架重新摆了一遍,外人几乎注意不到,但物流效率突然就上去了。Mozilla 这次让 Firefox 构建速度变快,属于后者。
根据开发者 farre 公开的技术说明,Firefox 在 Linux 上做一次 ./mach build,如果使用 buildcache 并启用新的 WebIDL 缓存包装器,热缓存下的 clobber build 时间可以从 1 分 27 秒进一步缩短到 1 分 12 秒。单看这 15 秒,好像谈不上惊天动地;但如果对比无缓存时约 5 分 35 秒的完整构建,整体已接近 17% 的进一步提升。对天天反复构建浏览器的开发者来说,这不是“能不能喝杯咖啡”的问题,而是一天里能少被打断多少次心流。
更关键的是,这次提速瞄准的并不是大家熟悉的 C++ 编译环节,而是 Firefox 构建早期的一个 Python 代码生成步骤:python3 -m mozbuild.action.webidl。它会读取大量 .webidl 文件,生成成千上万份 C++ 绑定代码、头文件、事件实现和状态文件。这个步骤本身不算最慢,却几乎每次全量构建都会跑,而且输出是确定性的——输入不变,结果就不变。工程师看到这种特征,眼睛都会亮:这不就是缓存的理想目标吗?
一段被忽视的 WebIDL,为什么能成为提速关键
如果你不是浏览器内核开发者,WebIDL 这个名字可能有点陌生。简单说,它是 Web 平台接口的“说明书语言”,浏览器会根据这些定义,自动生成 JavaScript 和底层 C++ 之间的绑定代码。网页里那些看似理所当然的 API,比如事件对象、DOM 接口、浏览器暴露给脚本的各种能力,很多都离不开这层自动生成的胶水代码。
问题也恰恰出在这里。过去开发者常用的 ccache、sccache,主要盯着“真正的编译器调用”,比如 gcc、clang、rustc。它们很擅长缓存编译产物,却不太关心构建系统里那些绕来绕去的辅助步骤。WebIDL 代码生成偏偏就是这样一种尴尬角色:它不是编译器,但又生成了大量后续编译要用的文件;它不是最重的活,却像上工前必须先开的仓库门。
Mozilla 这次合入的 Bug 2027655,本质上做了一件很朴素但很聪明的事:在 dom/bindings/Makefile.in 里,条件性地把 $(CCACHE) 这个命令包装器传给 WebIDL 的 Python action。于是原本直接运行的 python3 -m mozbuild.action.webidl ...,在启用 buildcache 的情况下,变成了 buildcache python3 -m mozbuild.action.webidl ...。从代码上看,这改动很小,小到像是一行“顺手的 plumbing”;但工程收益往往就藏在这种细节里。
技术上真正让这件事成立的,是 buildcache 的 Lua 插件机制。它允许开发者为那些“不是传统编译器”的命令写自定义包装器,告诉缓存系统三件事:这条命令我能不能识别、它的输入有哪些、它会生成哪些输出。farre 为 Firefox 写的 webidl.lua,就是把这套规则喂给了 buildcache。这样一来,系统就能对 .webidl 源文件、Python 代码生成脚本及相关依赖做哈希,命中时直接回放结果,未命中才真的去执行生成流程。
buildcache 为什么突然显得比 ccache、sccache 更“懂工程”
从这组数据看,buildcache 的优势已经不只是“另一个编译缓存工具”那么简单了。测试里,无缓存的 Firefox clobber build 约为 5 分 35 秒;ccache 热缓存是 3 分 21 秒;sccache 热缓存是 2 分 49 秒;buildcache 热缓存能做到 1 分 27 秒,而加上 WebIDL Lua 包装器后进一步来到 1 分 12 秒。
当然,作者也很诚实地强调,这只是同一台机器上的单次测试,不是严格意义上的大规模基准跑分。可即便如此,趋势已经足够鲜明:当现代软件工程越来越依赖代码生成、脚本驱动和多语言构建时,只会缓存“编译器”本身的工具,边际收益正在被压缩。真正高效的缓存,不该只理解 clang 和 rustc,还得理解 Python action、IDL 生成器、协议编译器、资源打包器,甚至测试前置步骤。
这也是我觉得这条新闻有行业意味的地方。过去十几年,开发者总把“编译慢”归因于 C++ 太重、模板太多、链接太长。可今天的大型软件项目——浏览器、移动系统、云原生平台、AI 基础设施——构建链条变得越来越长,越来越像一条流水线。流水线里最慢的地方,不一定是那台最显眼的机器,而可能是角落里一个每次都要重复干活的小机器人。Firefox 这次优化,像是在提醒大家:构建系统的性能瓶颈,已经进入“碎片化时代”。
顺便说一句,这也是为什么 buildcache 这类工具最近看起来更有想象力。相比 ccache 的老派稳健、sccache 的分布式缓存思路,buildcache 把 Lua 插件开放出来,等于承认现实:没有哪个缓存工具能原生理解所有构建动作,那就干脆让工程师自己教它。这种设计未必最省心,却更接近真实世界的复杂度。
15 秒到底值不值?对普通用户没感觉,对开发者却很“肉疼”
很多人看到 1 分 27 秒缩短到 1 分 12 秒,第一反应可能是:就这?这点时间也值得写一篇博客?
如果你只是下载 Firefox 安装包的普通用户,确实没什么感觉。但如果你是浏览器开发者,或者在维护一个巨型代码库,这 15 秒可能会在一天里反复出现几十次。人对等待极其敏感,尤其是那种“刚准备验证一个小改动,却得站在终端前发呆”的等待。工程管理里有个常识:只要单次反馈延迟降低一点点,开发者的实验意愿、修改频率和调试节奏都会发生变化。软件质量很多时候不是靠一次大跃进提升的,而是靠无数次“改完马上验证”的小循环堆出来的。
更何况,这还只是一个开始。WebIDL 只是 Firefox 构建中第一类被这样缓存的 Python action。farre 在文中已经点明,后面还有其他代码生成步骤也适合用同样的方法处理。换句话说,这 15 秒不是终点,更像是把一扇门推开了一条缝。门后是什么?可能是更多代码生成缓存,也可能是 Mozilla 对 mach 和构建流程做更系统性的“步骤级缓存”改造。
不过这里也有一个值得思考的问题:缓存层级越多,构建系统就越复杂,排错也越麻烦。任何缓存都有失效策略、环境漂移、路径依赖和“明明改了东西却没重建”的潜在风险。buildcache 的 Lua 包装器很灵活,但灵活本身就是双刃剑——它要求包装器作者准确描述输入输出边界,否则缓存命中得越漂亮,结果可能错得越隐蔽。这也是为什么这类优化更适合工程纪律较强、测试体系较全的项目,而不是随手堆脚本的小团队。
这不只是 Firefox 的小修小补,而是整个构建工具链的一个风向
把视线拉远一点,这件事发生在 2026 年,其实很符合当下软件工程的趋势。AI 训练越来越贵,算力越来越紧,大家都在谈“推理效率”“芯片利用率”;但在开发者工作台上,另一种效率战争同样激烈——那就是构建时间、反馈速度和本地迭代成本。大公司烧钱买 GPU,小团队和开源社区则在想方设法从工具链里抠性能。两者看上去离得很远,底层逻辑却一样:别把机器时间浪费在重复劳动上。
Mozilla 这些年在浏览器市场的压力不小,份额上很难和 Chromium 阵营正面硬拼。可 Firefox 仍然有一块非常宝贵的资产:它是少数还在认真打磨独立浏览器引擎、愿意把工程细节摊开给社区看的项目。像这次 WebIDL 缓存这种改动,不会登上大众头条,却恰恰体现了开源项目的生命力——没人会为一个“15 秒优化”专门开发布会,但总有人愿意为了整个工程生态把这 15 秒一点点抠出来。
我个人的判断是,这类“非编译器步骤缓存化”会越来越普遍,尤其是在大型 C++/Rust/JavaScript 混合项目中。今天是 Firefox 的 WebIDL,明天可能是 protobuf、flatbuffers、CSS 预处理、IDL 桥接、代码扫描、文档生成,甚至部分测试固件生成。未来的构建优化,拼的不是谁把编译器压榨得最狠,而是谁能把整条流水线看得最完整。
如果说过去软件构建工具在比谁更像一个优秀的“仓库管理员”,那么接下来,它们更像是“物流调度员”——理解每一辆车、每一批货、每一个中转环节。Firefox 这次的进展不算戏剧化,却很有启发性。工程世界里,很多真正重要的变化,往往不是一声巨响,而是终端里少等了 15 秒。