Zig 主分支刚合入一组构建系统重构。最扎眼的数字是:zig build -h 在示例场景里,wall time 从约 150ms 降到约 14.3ms,CPU cycles 和 instructions 都下降约 95%。
但这个数字不能当广告词用。它测的是 --help,而且踩中了缓存序列化配置的路径。真正该看的不是“快了多少”,而是 Zig 把 build.zig 从一个能统管配置和执行的脚本,改成了更窄的角色。
改了什么:build.zig 退到配置阶段
旧机制很直接:build.zig 和构建系统实现一起编进同一个 Debug 进程。脚本生成构建图,build runner 接着执行。
新机制切成两段。build.zig 被编成小型 configurer,输出二进制配置文件。maker 读取这份配置,再执行构建图。maker 以 Release 模式编译,并按 Zig 版本进入全局缓存。
| 维度 | 旧机制 | 新机制 |
|---|---|---|
| 配置阶段 | build.zig + 构建系统一起编译 | build.zig 编成小型 configurer |
| 构建图 | 留在内存里 | 序列化为二进制配置文件 |
| 执行阶段 | build runner 同进程执行 | maker 读取配置后执行 |
| 编译模式 | 整体 Debug | configurer Debug,maker Release |
| 缓存边界 | 更容易重复编译、重复执行 | maker 按 Zig 版本进入全局缓存 |
| 参数透传 | 脚本可通过 b.args 观察 | 观察能力移除,推荐 run_cmd.addPassthruArgs() |
这次最直接受益的,是两类人。
一类是经常改 build.zig 的项目维护者。脚本变动后,重复编译和重复执行的范围有机会缩小。尤其是配合 --watch、--fuzz、--webui 的项目,开发循环会更敏感地吃到收益。
另一类是工具作者,尤其是 ZLS 这类第三方工具。序列化配置如果能被直接消费,工具就不必长期维护一套 fork build runner。这里仍要收住:目前只能说作者预期会减少这类负担,不能写成 ZLS 已经完成适配。
对普通项目作者,动作很明确:检查 build.zig 里有没有依赖 b.args 观察透传参数的写法。若有,就要改向 run_cmd.addPassthruArgs()。如果项目把 build.zig 当小型控制中心用,0.17 前后最好预留一次迁移窗口,而不是等 CI 先炸。
为什么重要:构建脚本不再统治构建过程
我更在意的是边界变化。
过去的 build.zig 很灵活。它既是配置,也是程序,还能在构建过程里观察更多东西。灵活的另一面,是缓存难做,工具难接,构建事实也难稳定下来。
新机制把事情拆开:build.zig 生成配置,maker 执行构建图。脚本退后半步,构建系统拿回执行权。
这不是单纯抠几十毫秒。它是在给 Zig 后面的工具链复杂度提前修路。IDE、语言服务器、持续构建、watch 模式,都需要一份可复用、可分析的构建事实。只靠脚本副作用,工具只能猜。
这条路在构建系统历史里并不新。Make、Ninja、Bazel 都绕不开同一个问题:构建如果只是任意脚本的副作用,系统就很难判断什么该重跑,什么能复用。所谓“名不正则言不顺”。构建事实没有固定形状,缓存就没有稳固落点。
Zig 这次做对的地方,是没有把性能优化停在表层。它先把权责切开:配置是配置,执行是执行。速度来自这个边界,而不是魔法。
代价也在这里。
b.args 被拿掉,说明构建脚本的自由度开始收紧。过去脚本可以窥探透传参数,现在不能了。好处是改这些透传参数时,不必重新从源码构建脚本;坏处是某些旧写法要改。
我不太买账“少一个观察能力就是倒退”。工具链成熟的过程,常常就是把随意性换成可缓存、可复现、可分析的结构。脚本越强,系统越弱;脚本愿意退位,生态才有空间长出更可靠的工具。
接下来观察什么:0.17 的迁移痛感
这次变更还在主分支。0.17.0 预计几周内发布,0.17.1 仍会继续修补。它不是稳定版尘埃落定,而是发布前的现实压力测试。
接下来要看三个点。
| 观察点 | 为什么关键 | 对项目作者的动作 |
|---|---|---|
b.args 迁移成本 | 这是已知破坏点 | 搜索旧写法,改用 run_cmd.addPassthruArgs() |
--watch / --fuzz / --webui 体验 | 最容易体现缓存边界收益 | 在真实项目里测,不要只看 zig build -h |
| ZLS 等工具适配 | 决定序列化配置能否变成生态收益 | 等工具链说明,不要假设已经支持 |
这也是我对这次重构的判断:方向是对的,结账还没开始。
如果 0.17.0 把迁移痛感压住,0.17.1 又能及时补坑,用户会把它记成一次有效的基础设施治理。若迁移文档不清、边角 API 继续割手,这件事就会变成另一种记忆:构建系统又教育了用户一次。
回到开头那个 14.3ms。它有价值,但不是万能结论。它只说明在特定缓存路径下,拆分 configurer 和 maker 能把重复成本压得很低。
更大的信号是:Zig 开始把构建系统当基础设施管,而不是当脚本入口修。对一个还在快速演进的语言来说,这比一次漂亮 benchmark 更要紧。
