构建系统重构

Lobsters Hottest 工具

摘要

Zig 构建系统已经重构,将配置器和制造器进程分离,支持缓存、发布模式编译,并且'zig build'命令速度提升高达90%。这一变化提高了性能,并允许构建系统在不减速的情况下增加功能。

<p><a href="https://lobste.rs/s/sgqbvg/build_system_reworked">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/05/27 03:27

# 开发日志 ⚡ Zig 编程语言 来源: https://ziglang.org/devlog/2026/ 本页列出了 Zig 主分支的最新变更。 本页记录2026年的条目。其他年份可在开发日志存档页 (https://ziglang.org/devlog/) 查看。 2026年5月26日 ## 构建系统重写 (https://ziglang.org/devlog/2026/#2026-05-26) 作者:Andrew Kelley 一个大型分支刚刚合并:将执行器进程与配置器进程分离 (https://codeberg.org/ziglang/zig/pulls/35428) 这篇开发日志本质上是对即将发布的版本说明的预览,但同时也是向那些希望帮助测试新功能、提供反馈以指导 Zig 项目未来方向的人发出的预先通知。 在此之前,`build.zig` 文件以及构建系统的实现全都被编译到一个臃肿的进程中,以调试模式运行。当 `build.zig` 逻辑在内存中构建完构建图后,"构建运行器"代码执行它。 现在,`build.zig` 文件被编译成一个轻量级进程("配置器"),以调试模式运行。当这个逻辑在内存中构建完构建图后,将其序列化为一个二进制配置文件。父级 `zig build` 进程能感知到这个文件并将其缓存,供下次使用。在等待这一切完成的同时,它异步地以发布模式编译构建图执行进程("执行器")。一旦配置文件准备就绪且执行器进程编译完成,就执行执行器进程,并将配置文件传递给它。由于全局缓存的存在,执行器进程每个 `zig version` 只需编译一次。随后,执行器进程执行包含在序列化配置文件中的构建图。 这一变化的主要动机是让 `zig build` 更快,体现在三个方面: 1. 用户修改了 `build.zig` 逻辑后,只会重新编译这一部分,而无需连同整个构建系统一起重新编译。随着我们引入了 `--watch`、`--fuzz` 和 `--webui`,这一点变得越来越有价值。构建系统可以增加更多功能,而不会让 `zig build` 花费更长时间。 2. 现在,当构建系统知道没有任何变化时(例如为 `zig build` 命令行添加 `-freference-trace` 时),它可以完全跳过重新运行 `build.zig` 逻辑,直接使用上次的相同配置。 3. 现在,实际执行构建图的进程是在启用优化的情况下编译的。 为了演示第2点和第3点,以下是运行 `zig build --help` 前后的差异: ``` Benchmark 1 (34 runs): master/zig build -h measurement mean ± σ min ... max outliers delta wall_time 150ms ± 5.52ms 145ms ... 165ms 4 (12%) 0% peak_rss 84.8MB ± 275KB 84.2MB ... 85.1MB 0 ( 0%) 0% cpu_cycles 593M ± 4.01M 588M ... 608M 2 ( 6%) 0% instructions 995M ± 52.5K 995M ... 995M 0 ( 0%) 0% cache_references 25.8M ± 165K 25.4M ... 26.1M 0 ( 0%) 0% cache_misses 651K ± 20.1K 619K ... 697K 0 ( 0%) 0% branch_misses 918K ± 7.44K 906K ... 935K 0 ( 0%) 0% Benchmark 2 (348 runs): branch/zig build -h measurement mean ± σ min ... max outliers delta wall_time 14.3ms ± 744us 13.2ms ... 23.3ms 8 ( 2%) ⚡- 90.4% ± 0.4% peak_rss 78.5MB ± 562KB 77.1MB ... 81.4MB 7 ( 2%) ⚡- 7.4% ± 0.2% cpu_cycles 24.1M ± 821K 22.8M ... 27.1M 3 ( 1%) ⚡- 95.9% ± 0.1% instructions 43.7M ± 23.8K 43.7M ... 43.8M 56 (16%) ⚡- 95.6% ± 0.0% cache_references 1.46M ± 14.6K 1.40M ... 1.50M 19 ( 5%) ⚡- 94.3% ± 0.1% cache_misses 142K ± 4.87K 127K ... 157K 2 ( 1%) ⚡- 78.1% ± 0.4% branch_misses 126K ± 1.37K 120K ... 129K 12 ( 3%) ⚡- 86.3% ± 0.1% ``` 效果非常显著,因为之前每次 `zig build` 命令都会执行 `build.zig` 逻辑,而现在构建系统使用的是缓存的序列化配置。 除了性能之外,我还期望第三方工具(如 ZLS)能够通过直接读取序列化配置文件获得好处,而无需维护构建运行器的分支。 这个变更集对 Zig 构建系统的内部机制进行了大量重写,但从 API 角度来看,除了 PR 中提到的例外情况外,基本没有破坏性变化。 对于大多数人来说,这可能是他们遇到的主要破坏性变化: ```zig if (b.args) |args| { run_cmd.addArgs(args); } ``` ⬇️ ```zig run_cmd.addPassthruArgs(); ``` 这从构建脚本中移除了一项能力:它们不能再观察到这些参数。作为交换,当更改这些参数时,构建脚本不再需要从源码重新构建。 如果你希望影响 Zig 的未来方向,现在是升级项目到开发版本并试用这些变化的好时机。我们将在几周内发布 0.17.0。不过,如果你没有时间,发现 0.17.0 破坏了你的构建,也不用担心,我们会在 0.17.1 标签中进行修复。 2026年4月8日 ## 使用 LLVM 进行增量编译 (https://ziglang.org/devlog/2026/#2026-04-08) 作者:Matthew Lugg 上个月合并了我的类型解析改动 (https://ziglang.org/devlog/2026/#2026-03-10) 之后,我花了一些时间在个人项目上。不过最近我确实抽出了一些时间对 LLVM 代码生成后端进行了一些改进。这涉及几个不同目标的增强,但一个对用户友好的变化是,我成功让 LLVM 后端实现了增量编译。 遗憾的是,这并不能加快令人头疼的 "LLVM Emit Object" 阶段:这段时间完全取决于 LLVM。然而,增量编译确实有助于缩短实际 Zig 编译器代码的耗时。这意味着如果你的代码存在编译错误(因此会跳过 "LLVM Emit Object" 阶段),你通常能非常快速地得到这些错误。(当然,在成功构建时它也会带来一些速度提升。) 这项支持目前已在 master 分支构建中可用,并将包含在 0.16.0 版本中(我们很快就会打标签)。 对于还没有尝试过的人,尤其是如果你正在使用 Zig 的 master 分支,请一定尝试在 `zig build` 中添加 `-fincremental --watch` 来体验增量编译!Zig 核心团队已经在我们自己的开发流程中受益于增量编译一年多了,我们也收到了用户的积极反馈。这个功能目前相对稳定,很多人惊讶于他们可以在毫秒而非秒级内获得最新的编译错误,从而节省大量时间。 我个人还没有真正用过 LLVM 后端的增量编译,但现在 CI 中所有增量测试覆盖率都已为 LLVM 后端启用,而且我收到了用户的积极反馈,所以绝对值得一试。和往常一样,如果你在增量编译中遇到错误,请报告它们,如果可能的话! 谢谢,希望这对你有用 :) 2026年3月10日 ## 类型解析重新设计,附带语言变化 (https://ziglang.org/devlog/2026/#2026-03-10) 作者:Matthew Lugg 今天,在经过两个(甚至可以说是三个)月的工作后,我合并了一个30000行的 PR (https://codeberg.org/ziglang/zig/pulls/31403)。这个分支的目标是将 Zig 编译器内部的类型解析逻辑重新设计为更符合逻辑、更直接的形式。就我个人而言,这是一个令人兴奋的变化,因为它让我清理了许多编译器内部代码,同时也带来了一些很好的用户可见变化,你可能会感兴趣! 一方面,Zig 编译器现在对类型字段的分析变得更"懒"了:如果某个类型从未被初始化,那么 Zig 就无需关心该类型的"样子"。这对于那些同时充当命名空间的类型来说非常重要(这是现代 Zig 中的常见模式)。例如,当使用 `std.Io.Writer` 时,你不希望编译器也拉入 `std.Io` 中的大量代码!下面是一个简单的例子: ```zig const Foo = struct { bad_field: @compileError("i am an evil field, muahaha"), const something = 123; }; comptime { _ = Foo.something; // `Foo` 仅用作命名空间 } ``` 此前,这段代码会触发编译错误。现在它可以正常编译,因为 Zig 从未真正查看过 `@compileError` 调用。 另一个改进是关于"依赖循环"的体验。任何之前遇到过 Zig 依赖循环编译错误的人都知道,错误消息完全没用——但现在已经改变了!如果你遇到一个(现在比以前少了一些),你会得到详细的错误消息,告诉你依赖循环的确切来源。看看这个: ```zig const Foo = struct { inner: Bar }; const Bar = struct { x: u32 align(@alignOf(Foo)) }; comptime { _ = @as(Foo, undefined); } ``` ``` $ zig build-obj repro.zig error: dependency loop with length 2 repro.zig:1:29: note: type 'repro.Foo' depends on type 'repro.Bar' for field declared here const Foo = struct { inner: Bar }; ^~~ repro.zig:2:44: note: type 'repro.Bar' depends on type 'repro.Foo' for alignment query here const Bar = struct { x: u32 align(@alignOf(Foo)) }; ^~~ note: 消除其中任意一个依赖即可打破循环 ``` 当然,依赖循环可能比这复杂得多,但在每个我测试过的案例中,错误消息都包含足够的信息可以轻松看清问题。 此外,这个 PR 对 Zig 编译器的"增量编译"功能做了很大改进。简而言之,它修复了大量已知的错误,但尤其是"过度分析"问题(即增量更新做了超出必要的工作,有时甚至多出很多)应该终于差不多被消除了——使得增量编译在许多情况下显著加快!如果你还没试过,考虑一下增量编译 (https://ziglang.org/download/0.15.1/release-notes.html#Incremental-Compilation):它真的是一种很棒的开发体验。这无疑是我最激动的一个改进,也是最初推动这一变化的重要原因。 这个 PR 还带来了许多其他变化——数十个错误修复、一些小的语言变化(大多比较小众)、以及编译器性能改进。这里列出来的太多了,如果你有兴趣了解更多,可以在 Codeberg 上看看这个 PR (https://codeberg.org/ziglang/zig/pulls/31403)——当然,如果你遇到任何错误,请务必打开 issue。Happy hacking! 2026年2月13日 ## io_uring 和 Grand Central Dispatch 的 std.Io 实现已合并 (https://ziglang.org/devlog/2026/#2026-02-13) 作者:Andrew Kelley 随着 0.16.0 发布周期的尾声,Jacob 一直在努力让 `std.Io.Evented` 跟上最新的 API 变化: - io_uring 实现 (https://codeberg.org/ziglang/zig/pulls/31158) - Grand Central Dispatch 实现 (https://codeberg.org/ziglang/zig/pulls/31198) 这两者都基于用户态栈切换——有时被称为 fiber(纤程)、"有栈协程" 或 "绿色线程"。 它们现在**可供实验**,通过使用 `std.Io.Evented` 构建应用程序即可。但应被视为**实验性**的,因为在它们能够可靠、稳健地使用之前,还有一些重要的后续工作需要完成: - 更好的错误处理 (https://codeberg.org/ziglang/zig/issues/31199) - 移除日志记录 - 诊断使用 `IoMode.evented` 为编译器编译时出现的意外性能下降 - 还有几个函数尚未实现 (https://codeberg.org/ziglang/zig/issues/31200) - 需要更多测试覆盖 - 需要一个内置函数来告知某个函数的最大栈大小 (https://github.com/ziglang/zig/issues/157),以便在禁用内存超额分配 (overcommit) 时这些实现也能实用。 考虑到这些警告,我们似乎确实在接近"应许之地"——Zig 代码可以轻松地切换 I/O 实现: ```zig const std = @import("std"); pub fn main(init: std.process.Init.Minimal) !void { var debug_allocator: std.heap.DebugAllocator(.{}) = .init; const gpa = debug_allocator.allocator(); var threaded: std.Io.Threaded = .init(gpa, .{ .argv0 = .init(init.args), .environ = init.environ, }); defer threaded.deinit(); const io = threaded.io(); return app(io); } fn app(io: std.Io) !void { try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n"); } ``` ``` $ strace ./hello_threaded execve("./hello_threaded", ["./hello_threaded"], 0x7ffc1da88b20 /* 98 vars */) = 0 mmap(NULL, 262207, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f583f338000 arch_prctl(ARCH_SET_FS, 0x7f583f378018) = 0 prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0 prlimit64(0, RLIMIT_STACK, {rlim_cur=16384*1024, rlim_max=RLIM64_INFINITY}, NULL) = 0 sigaltstack({ss_sp=0x7f583f338000, ss_flags=0, ss_size=262144}, NULL) = 0 sched_getaffinity(0, 128, [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31]) = 8 rt_sigaction(SIGIO, {sa_handler=0x1019d90, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x10328c0}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 rt_sigaction(SIGPIPE, {sa_handler=0x1019d90, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x10328c0}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 writev(1, [{iov_base="Hello, World!\n", iov_len=14}], 1Hello, World! ) = 14 rt_sigaction(SIGIO, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x10328c0}, NULL, 8) = 0 rt_sigaction(SIGPIPE, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x10328c0}, NULL, 8) = 0 exit_group(0) = ? +++ exited with 0 +++ ``` 仅替换 I/O 实现: ```zig const std = @import("std"); pub fn main(init: std.process.Init.Minimal) !void { var debug_allocator: std.heap.DebugAllocator(.{}) = .init; const gpa = debug_allocator.allocator(); var evented: std.Io.Evented = undefined; try evented.init(gpa, .{ .argv0 = .init(init.args), .environ = init.environ, .backing_allocator_needs_mutex = false, }); defer evented.deinit(); const io = evented.io(); return app(io); } fn app(io: std.Io) !void { try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n"); } ``` ``` execve("./hello_evented", ["./hello_evented"], 0x7fff368894f0 /* 98 vars */) = 0 mmap(NULL, 262215, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f70a4c28000 arch_prctl(ARCH_SET_FS, 0x7f70a4c68020) = 0 prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0 prlimit64(0, RLIMIT_STACK, {rlim_cur=16384*1024, rlim_max=RLIM64_INFINITY}, NULL) = 0 sigaltstack({ss_sp=0x7f70a4c28008, ss_flags=0, ss_size=262144}, NULL) = 0 sched_getaffinity(0, 128, [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31]) = 8 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f70a4c27000 mmap(0x7f70a4c28000, 548864, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0

相似文章

Zig 构建速度正在提升

Mitchell Hashimoto

Zig 0.15 相比 0.14 在编译时性能有显著提升,构建脚本编译时间从约 7 秒降至约 1.7 秒,完整构建时间从 41 秒降至 32 秒,且仍使用 LLVM。本文重点介绍了自托管后端和增量编译方面的进展。

Zig ELF 链接器改进开发日志

Hacker News Top

新的 Zig ELF 链接器现在支持外部库和 C 源码的快速增量编译,在 x86_64 Linux 上能够实现毫秒级重建。

Bun 的 Rust 重写已合并

Lobsters Hottest

Bun,JavaScript 运行时和包管理器,已合并其核心从 Zig 到 Rust 的重写,可能提升性能和可维护性。

Zig 0.16.0 发布说明:"Juicy Main"

Simon Willison's Blog

Zig 0.16.0 发布了一个名为 'Juicy Main' 的新特性,它为 main() 函数提供了依赖注入功能,方便地访问分配器、IO、环境变量和命令行参数。

2026 年的 Zig 与 Rust

Lobsters Hottest

本文在 2026 年的背景下对比了 Zig 和 Rust,认为编程代理通过自动化生成 Rust 代码,削弱了 Zig 在人机交互体验上的优势。