加速开发周期:Zig 的全新 ELF Linker 与构建系统大改版
系统级语言的开发者体验通常由性能与迭代速度之间的张力来定义。多年来,“编译-链接-运行”周期一直是 C 和 C++ 开发中的一个重大瓶颈。Zig 正在积极攻克这一瓶颈,不仅通过编译器优化,还通过重新思考整个工具链——从构建图的执行方式到最终二进制文件的链接方式。
Zig main branch 的最新更新揭示了通过全新的 ELF linker 和对 zig build 过程的基础性重构,致力于为系统编程带来“脚本语言级的速度”。
全新的 ELF Linker:毫秒级迭代
近期开发日志中最显著的里程碑之一是新 ELF linker 的进展(通过 -fnew-linker 启用)。虽然最初仅限于 Zig-only 代码,但该 linker 已演进到可以支持外部库、C 源码,甚至在使用 LLVM 和 LLD 构建时支持自托管的 Zig 编译器本身。
该实现的核心特性是快速增量编译。在 x86_64 Linux 上,开发者现在可以实现数十毫秒级的增量重新构建。例如,对项目的微小改动可以在大约 30ms 内完成重新构建,这种速度将开发工作流从一系列停顿转变为连续的流式体验。
当前局限性与路线图
尽管取得了速度提升,但新 linker 尚未成为默认工具链的完全替代品。最显著的缺失特性是无法为 Zig 代码生成 DWARF 调试信息。在实现这一点之前,该 linker 主要适用于快速迭代和“打印调试(print debugging)”,而非使用调试器进行深度会话调试。
重构构建系统
为了配合更快的 linker,Andrew Kelley 引入了构建系统的一次重大架构变更。此前,build.zig 文件和构建系统的实现会在 Debug 模式下被编译成一个臃肿的单一进程。新架构将其分为两个截然不同的角色:
- 配置器 (The Configurer):
build.zig逻辑被编译成一个小型进程,用于在内存中构建构建图并将其序列化为二进制配置文件。 - 构建器 (The Maker):一个使用 Release 模式编译的独立进程,负责执行序列化后的构建图。
这种分离带来了三个主要的性能优势:
- 减少编译量:变更时仅需编译用户的
build.zig逻辑,而非整个构建系统。 - 缓存配置:如果构建参数没有变化,Zig 可以完全跳过运行
build.zig逻辑的过程,直接使用缓存的二进制配置。 - 优化执行:实际执行构建图的进程现在经过了优化,从而大幅缩短了简单命令的实际耗时(例如,
zig build -h从 150ms 降至 14.3ms)。
深度解析:编译器与标准库的改进
除了构建系统和 linker,Zig 还在精炼其内部逻辑,以提高稳定性和性能。
类型解析与延迟分析
类型解析逻辑的重新设计使编译器变得更加“懒惰”。如果一个类型从未被初始化,Zig 现在会避免分析该类型的字段。这对于用作命名空间的类型特别有利,可以防止编译器在从未实际访问过的字段中触发 @compileError 调用。
Windows 原生 API 偏好
为了减少臃肿和不可预测的行为,Zig 标准库正转向优先使用原生 API 而非 Win32 封装。通过绕过 kernel32.dll 并转而使用 ntdll.dll,Zig 避免了不必要的堆分配和隐藏的失败模式。
例如,在熵生成方面,Zig 现在可以通过直接与 \Device\CNG 交互来避免 advapi32.dll 和 bcryptprimitives.dll 的开销,从而消除在 CI 环境中观察到的非确定性延迟和潜在的加载失败。
“zig libc” 项目
Zig 正在逐步用 Zig 标准库封装器替换内置的 C 源码文件。这减少了项目对 C 语言和第三方项目的依赖,同时提高了编译速度和二进制文件大小。由于这些函数共享 Zig 编译单元 (ZCU),编译器可以跨越 libc 边界进行优化——有效地提供了类似于 LTO 的收益,而无需传统 linker 的后期开销。
社区视角与展望
社区的反应突显了人们对 Zig 作为高性能领域中 C 语言可行替代品的日益增长的认知。正如一位贡献者所指出的,原生 linker 与增量编译的结合,可以让开发者“以 JS 或 Python 的迭代速度,获得 C 或 Rust 的性能”。
虽然一些用户表达了希望发布稳定的 1.0 版本以鼓励企业采用的愿望,但目前的轨迹表明,Zig 正在专注于“底层架构(plumbing)”——即 linker、构建系统和 libc 实现——以确保当 1.0 到来时,其基础是绝对快速且无可挑剔的。