Zig 增量编译:毫秒级重构的秘密

Zig's Incremental Compilation Internals

Zig 增量编译:毫秒级重构的秘密

作为 Zig 核心团队成员,我亲历了增量编译功能从概念验证到日常实用的全过程。这项技术让编译器能精准识别变更的函数与声明,仅重新编译必要代码并直接修补二进制文件,将复杂应用的修改重建时间压缩至毫秒级。在演示中,Fizzy 像素编辑器的初始构建耗时约 5 秒,而后续每次修改的重建仅需 50 到 70 毫秒。文章深入剖析了从源文件处理、ZIR 中间表示生成,到语义分析中依赖图构建的底层机制,揭示了 Zig 如何通过语言设计优化,将编译过程拆解为可独立分析的单元,从而实现极致的编译速度。

今天,使用 Zig 的增量编译,你可以在毫秒级的时间内对真实的复杂应用进行修改。
  1. steveklabnik

    Zig 的工具链工作一直令人印象深刻。虽然我目前不打算用它写软件——毕竟我认为内存安全是基本门槛——但所有这些工作都非常、非常棒。在增量编译工作之前,是工具链和交叉编译器的工作。工具链方面一直表现极佳。我很期待他们接下来会带来什么!

    > 语义分析是编译器中最难实现增量化的部分。因此毫不奇怪,语言设计在这里变得至关重要:虽然我很确信大多数现代语言都能像我们这样支持类似的增量编译,但某些设计决策会让这变得困难得多。Zig 多年来对其设计进行了调整(有时颇具争议),专门为了更容易支持快速的增量编译。

    这是我希望 Rust 当初也能做到的。不过,不可能一次性把所有事情都做好,我们当时已经有很多事情要处理了。这也是“何时发布 1.0 版本”的权衡之一;就我们的语言目标而言,2015 年是推出的正确时机,但如果能再多打磨几年,也许能让编译时间快得多。软件工程真难啊。

  2. afdbcreid

    这篇文章真的很有趣。作为 rust-analyzer 团队的一员,我无法不将其与 Rust 生态的现状进行比较。Rust 以拥有(甚至更)复杂的增量编译系统而闻名,但其编译速度却慢得多。我认为这主要归因于两点:

    - 语言设计。Zig 是为快速和增量编译而设计的,Rust 则不是。例如,文中提到 Zig 有四个属性(布局、类型、值、函数体)需要编译器跟踪变化。Rust 的属性要多得多,以至于静态跟踪它们根本不可能,因此编译器使用查询系统来动态跟踪它们,这增加了开销。

    - 编译器实现。Rust 比 Zig 更难编译,rustc 不仅更老、更大(代码行数多出 10 到 20 倍),这使得对其进行修改要困难得多。

  3. thefaux

    关于这个设计,有一点我不太明白:为什么他们坚持为调试构建生成一个包含所有代码的巨大二进制文件?在我看来,更简单的做法是生成许多更小的共享库(也许在文件级别),然后将它们链接到最终的二进制文件中。采用这种方法,程序二进制文件的 text 段会非常小,并附带一个(可能很长的)共享库加载列表。但即使要加载数千个共享库,生成的程序二进制文件也不会太长,而且根本不需要二进制补丁。

    我理解在发布模式下,单个巨大的二进制文件可能是可取的,但我很难理解这种设计用于调试构建。此外,在阅读这篇文章时,我不禁想到如果主二进制文件损坏了怎么办。也许用户在打补丁时用 ctrl+c 取消了编译。即使他们有避免和/或检测损坏的方案,一开始就不打补丁、始终生成一个新的主二进制文件要简单得多。这同样是合理的,因为新二进制文件主要只是一个要链接的共享库列表,不会占用太多空间,可以快速写入磁盘。此外,这个过程可以递归进行,例如在子目录级别,这样在增量链接时可能会生成几个相当小的共享库,而不是打补丁插入新代码并写入级联重定位。

  4. anitil

    自从了解到 `zig cc` 可以作为入门 Zig 的方式后,我就成了 Zig 的忠实粉丝。我之前就对它的构建缓存印象深刻,所以很想试试这个功能。

  5. patrec

    > 运行时函数函数体上的依赖是不可能的(至少在我这里展示的简化观点中)

    考虑到例如常量可以由 comptime 函数计算,这该如何运作呢?

  6. sigbottle

    我一直觉得这很迷人,但我所知道的增量编译仅限于一些冷门编程语言和 Rust。哦对了,我想 Roslyn 也算吧?

    这真是一个有趣且迷人的问题,值得深入研究。

  7. hoppp

    Zig 编译器可以编译 C 代码,那它对 C 代码也有效吗?

  8. remywang

    这对发布构建有效,还是目前仅对调试构建有效?

同日更多故事

2026-07-28