加速开发周期: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 模式下被编译成一个臃肿的单一进程。新架构将其分为两个截然不同的角色:

  1. 配置器 (The Configurer)build.zig 逻辑被编译成一个小型进程,用于在内存中构建构建图并将其序列化为二进制配置文件。
  2. 构建器 (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.dllbcryptprimitives.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 到来时,其基础是绝对快速且无可挑剔的。

Sources