Rust 编译慢、target/ 目录大,这是老生常谈了。这篇记录一下我目前在用的几个改善手段,以我的项目 ClewdR(394 个 crate 依赖的异步 Web 服务)为例。
环境:Rust 1.94.1,CachyOS (Arch-based),NVMe SSD,Btrfs。
rust-lld
链接是 Rust 编译的最后一步,也是传统上最慢的一步。GNU ld 在这里表现很差,特别是开了 LTO 的时候。
以前的做法是手动装 lld 或 mold,然后在 .cargo/config.toml 里配:
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
但从 Rust 1.85(2025-02-20)开始,rust-lld 在 x86_64-unknown-linux-gnu 上已经是默认链接器了,不需要任何配置:
$ readelf -p .comment target/release/clewdr
String dump of section '.comment':
[ 1] Linker: LLD 21.1.8
[ 5f] rustc version 1.94.1 (e408947bf 2026-03-25)
升级 Rust 就行,免费午餐。
顺带一提,社区里还有两个值得关注的替代链接器:
- mold:以速度为卖点的链接器,在非 LTO 场景下通常比 lld 更快,不过对 LTO 的支持有限。
- wild:一个用 Rust 写的实验性链接器,目标是成为最快的 Linux ELF 链接器,做了大量多线程优化。目前还在活跃开发中,有兴趣可以关注。
对于大多数人来说,默认的 rust-lld 已经够用了。
sccache
sccache 是编译缓存,类似 ccache 但支持 Rust。它缓存每个 crate 的编译产物,相同输入直接复用,不再重新编译。
配置很简单,装好 sccache 后在 ~/.cargo/config.toml 加一行:
[build]
rustc-wrapper = "sccache"
以 ClewdR 的 release 构建为例(opt-level = "z", lto = true, codegen-units = 1):
| 场景 | 耗时 |
|---|---|
| 无 sccache,clean build | 48.4s |
| sccache 冷缓存,clean build | 55.7s |
| sccache 热缓存,clean build | 34.2s |
首次构建因为要写缓存会稍慢,之后 clean build 快了约 30%。热缓存下的 cache hit rate:
Cache hits rate 52.00 %
Cache hits rate (Rust) 54.43 %
Cache hits rate (C/C++) 50.00 %
主要收益场景:cargo clean 后重建、切分支、多项目共享依赖。proc-macro crate 不会被缓存。本地缓存默认 10 GiB,也支持 S3 / GCS 远程缓存。
Btrfs 透明压缩
ClewdR 一次 release 构建产生 5121 个文件、840 MiB。多个 Rust 项目的 target/ 加起来轻松几十 GB。

我的文件系统是 Btrfs,挂载选项带了 compress=zstd:3:
/dev/nvme0n1p2 on /home type btrfs (rw,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2)
对上层完全透明,不需要改任何构建配置。用 compsize 看 target/ 的实际磁盘占用:
Processed 5121 files, 7468 regular extents (7782 refs), 2710 inline.
Type Perc Disk Usage Uncompressed Referenced
TOTAL 41% 320M 768M 814M
none 100% 82M 82M 96M
zstd 34% 237M 685M 717M
768 MiB 的数据实际只占 320 MiB,不到原始大小的一半。编译中间产物(.o、.rlib、.rmeta)压缩效果尤其好。
有一点要注意:sccache 的缓存本身已经是压缩过的,Btrfs 再压几乎没有效果:
# ~/.cache/sccache/
Type Perc Disk Usage Uncompressed Referenced
TOTAL 99% 9.8G 9.8G 10G
zstd:3 在 NVMe SSD 上基本感受不到性能损失,是个不错的平衡点。
如果你用的是 ZFS,同样支持透明压缩,设置 compression=zstd 即可,效果类似。
文件去重
多个 Rust 项目之间的 target/ 目录往往有大量重复内容——相同版本的依赖编译出来的 .rlib、.rmeta 文件是完全一样的。透明压缩能缩小单个文件的体积,但对这种跨项目的重复无能为力,这时候就需要文件系统级别的去重了。
我本地 18 个 Rust 项目的 target/ 目录合计 Referenced 51 GiB,经过 Btrfs 去重(reflink)+ zstd 压缩后,实际磁盘占用只有 15 GiB:
Processed 165605 files, 274042 regular extents (562708 refs), 101289 inline.
Type Perc Disk Usage Uncompressed Referenced
TOTAL 47% 15G 32G 51G
none 100% 7.7G 7.7G 9.0G
zstd 30% 7.3G 24G 42G
其中去重把 51G 降到了 32G(省了约 37%),压缩再把 32G 降到 15G(省了约 53%),两者叠加效果相当可观。
Btrfs 支持离线去重(offline deduplication),原理是把内容相同的 extent 合并为同一份物理数据(reflink)。常用的工具有两个:
- duperemove:扫描指定目录,找到重复的 extent 后提交给内核去重。适合手动或定时跑一次。
- bees:后台守护进程,持续监控文件系统变化并自动去重。比 duperemove 更适合"设好就忘"的场景,但会持续占用一些 CPU 和内存。
ZFS 则内置了在线去重(inline dedup),设置 dedup=on 即可。比 Btrfs 的离线去重更激进——写入时就直接比对,重复数据根本不会落盘。代价是每个块都需要在内存中维护一条 DDT(Dedup Table)记录,数据量大的时候内存开销非常可观。一般建议内存充裕(比如 NAS / 服务器场景)再开,桌面机上慎用。
去重和前面的 sccache 看起来有点像,但侧重点不同:sccache 省的是时间,跳过重复编译直接从缓存取结果,但每个项目的 target/ 里该有的文件还是各自独立的副本;去重省的是空间,把这些内容相同的副本在磁盘上合并成一份。两者是互补的。
写在最后
Rust 编译时间和磁盘占用是社区诟病已久的问题。Rust Team 自己也在持续努力——rust-lld 默认启用、增量编译改进、前端并行化等等——但社区的反馈始终是"还不够快"。上面这些手段说到底都是 workaround,是在编译器本身没法一步到位的情况下,从外围能做的一些补救。而且老实说,即使全部用上,Rust 的编译体验对比大多数其他语言依旧几乎是最慢、空间占用最大的那一档。只能说,这就是为零成本抽象和所有权检查付出的代价吧。