在 Linux nightly 版本上使用 `rust-lld` 加快链接时间

2024年5月17日 · Rémy Rakic 代表 编译器性能工作组

概要:在 `x86_64-unknown-linux-gnu` 的 nightly 版本上,rustc 将默认使用 rust-lld,以显著减少链接时间。

一些背景

链接时间通常是编译时间的重要组成部分。当 rustc 需要构建二进制文件或共享库时,它通常会调用系统上安装的默认链接器来完成此操作(这可以在命令行上或为编译代码的目标进行更改)。

链接器执行重要的工作,需要考虑稳定性、向后兼容性等等。由于这些以及其他原因,在最流行的操作系统上,它们通常是较旧的程序,在计算机只有单核时设计的。因此,它们在现代机器上通常会很慢。例如,在 Linux 上以调试模式构建 ripgrep 13 时,大约一半的时间实际上都花费在链接器上。

然而,存在不同的链接器,改进链接时间的常见建议是使用这些更新更快的链接器之一,例如 LLVM 的 lld 或 Rui Ueyama 的 mold

Rust 的一些 wasm 和 aarch64 目标已经默认使用 lld。当使用 rustup 时,rustc 会为此目的附带一个 lld 版本。当 CI 构建 LLVM 以在编译器中使用时,它还会构建链接器并将其打包。它被称为 rust-lld,以避免与用户机器上已安装的任何 lld 冲突。

由于链接时间的改进是显著的,因此在最流行的目标中使用它会是一个很好的默认设置。这个问题已经被讨论了很长时间,例如在 #39915#71515 中,rustc 已经提供了 nightly 标志来使用 rust-lld

到目前为止,我们相信我们已经在 CI、crater 和我们的基准测试基础设施上完成了所有可以进行的内部测试。我们现在希望扩大测试范围,并收集真实的反馈和用例。因此,我们将启用 rust-lld 作为 `x86_64-unknown-linux-gnu` 的 nightly 版本默认使用的链接器。

好处

虽然这也使编译器能够在未来使用更多的链接器功能,但最直接的好处是链接时间的大幅缩短。

以下是上述 ripgrep 示例的更多详细信息:链接时间减少了 7 倍,最终端到端编译时间减少了 40%。

Before/after comparison of a ripgrep debug build

大多数二进制文件都会在这里看到一些改进,但对于更大的二进制文件或涉及 debuginfo 的情况,尤其显著。这些通常会在链接器中遇到瓶颈。

这是 一个链接,指向我们基准测试的完整结果。

如果测试进展顺利,我们就可以稳定下来,为 `x86_64-unknown-linux-gnu` 用户默认使用这个更快的链接器,然后再考虑其他目标。

可能的缺点

从我们之前的测试来看,我们实际上并不期望在实践中出现问题。它是绝大多数情况下的直接替代品,但是 lld 与 GNU ld 并不是bug 对 bug 兼容的。

在任何情况下,如果出现任何问题,都可以禁用 rust-lld:使用 -Z linker-features=-lld 标志恢复使用系统的默认链接器。

一些以某种方式依赖这些差异的 crate 可能需要额外的链接参数。例如,我们在 crater 运行中看到 <20 个 crate 由于关于 封装符号的不同默认值而无法链接:这些可能需要 -Clink-arg=-Wl,-z,nostart-stop-gc 来匹配传统的 GNU ld 行为。

性能大幅提升的一部分来自于并行性,这在资源受限的环境中可能是不受欢迎的。

总结

rustc 将在 `x86_64-unknown-linux-gnu` nightly 版本上使用 rust-lld,以大大缩短链接时间,从明天的 rustup nightly 版本(nightly-2024-05-18)开始。如果您遇到问题,请在 GitHub 上提出 issue

如果发生这种情况,您可以使用 -Z linker-features=-lld 标志恢复到默认链接器。可以通过将其添加到常用的 RUSTFLAGS 环境变量中,或者添加到项目的 .cargo/config.toml 配置文件中,如下所示

[target.x86_64-unknown-linux-gnu]
rustflags = ["-Zlinker-features=-lld"]