Rust 编译加速与产物体积优化:一份可复现的实测报告
本文目录23 节
这是一份实验报告,不是“最佳实践清单”。我想知道的是:网上流传的那些 Rust 编译提速与减体积建议,在这台机器上到底各自值多少。所以每一项都做了控制变量的实测,每个关键配置重复 3 次,报告原始数据而不只是结论。
最重要的两个发现提前说:
- 日常开发真正的加速不是加旗子,而是别用 release 迭代。 改一行代码,
devprofile 重编译 1.19 秒;开了lto = true的release要 18.27 秒——和“完全没改过”的 18.17 秒几乎一样,也就是 release 根本没有增量编译可用。 - Windows 上
strip = "symbols"是完全的无效操作。 开与关,exe 和.pdb的字节数一模一样。
下面是完整过程。
一、测试环境
rustc -vV# rustc 1.98.1 (48a229cea 2026-09-01)# host: x86_64-pc-windows-msvc# LLVM version: 22.1.8
cargo --version # cargo 1.98.1 (797e8a9bc 2026-08-05)rustc +nightly --version# rustc 1.100.0-nightly (feaadeeac 2026-09-19)cargo +nightly --version# cargo 1.100.0-nightly (495c385d9 2026-09-16)rustup --version # rustup 1.29.1 (d95a37b6a 2026-08-13)
nproc # 16| 项 | 值 |
|---|---|
| 操作系统 | Windows 10.0.19045 (x64) |
| CPU | 16 个逻辑核 |
| 工具链 | stable 1.98.1 + nightly 1.100.0 |
| 后端 | LLVM 22.1.8 |
| 磁盘 | C: 301 GB,测试期间剩余 93 GB |
环境勘察还确认了三件对实验有影响的事:
# 1. rust-lld 已经随 rustup 装在 sysroot 里,但不在 PATH 上rustc --print sysrootls "$(rustc --print sysroot)/lib/rustlib/x86_64-pc-windows-msvc/bin"# gcc-ld rust-lld.exe rust-objcopy.exe wasm-component-ld.exe ...
# 2. 系统 PATH 里没有 lld-link(VS 的 LLVM 组件没装)where lld-link # not on PATH
# 3. 全局 Cargo 配置是空的,本实验不受既有配置污染ls ~/.cargo/config.toml # (no such file)二、实验设计
2.1 被测对象
| 用途 | 项目 | 理由 |
|---|---|---|
| 编译速度 | du-dust 1.2.6(dust) |
真实项目、74 个依赖、自带 [profile.release] lto=true, codegen-units=1, strip=true,正好能对比“项目自带的重优化 profile”和“关掉优化的 profile”;源码已在本地 registry 缓存里,可离线复现 |
| 产物体积 | 自建单 crate(纯 std、零依赖) | 体积实验需要隔离单个开关的影响。真实项目的 profile 是一整套,分不清哪个开关贡献了多少 |
du-dust 的 release profile 原文(来自 crate 里的 Cargo.toml):
[profile.release]lto = truecodegen-units = 1strip = true源码副本的获取方式(不污染 registry,改完即弃):
cp -r ~/.cargo/registry/src/index.crates.io-*/du-dust-1.2.6 dust2cd dust2 && rm -f Cargo.lock删掉 Cargo.lock 是为了让 Cargo 在本地重新解析依赖,避免继承打包时的锁文件造成不可预期的差异。
2.2 冷编译的定义与测量方法
- 冷编译=用一个全新的
--target-dir编译,所有依赖从零构建。每测一轮先rm -rf该目录,确保不是复用了上一轮的产物。 - 计时来源=Cargo 自己打印的
Finished ... in Xs,不自己写秒表。原因很实际:我最初用date +%s%3N做 shell 计时,在这套 MSYS 环境下取值异常(差值算出了 1.6e18 ms 这种数字),而 Cargo 的输出精度到 10 ms,够用且没有 shell 层噪声。 - 每档 3 次,报告 中位数(抗离群值)与极差(观察稳定性)。单次测量在这类“几十秒级、受 OS 文件缓存和杀软影响”的基准上不足以支撑结论。
- 控制变量:测量期间不跑其它编译任务;所有命令带
--offline,杜绝网络抖动;每档独立 target 目录,避免互相污染指纹。
2.3 重复测量与匹配对照
需要注意一处设计缺陷并说明我的补测方法:nightly 与 stable 是不同版本,直接比较「stable 默认 41.66s vs nightly 并行 30.43s」会把“版本差异”算进“并行前端”的功劳。所以我额外补了 pairing 对照——在同一个 nightly 版本上分别测“不开并行前端”和“开并行前端”,只有这组对比才能归因给 -Zthreads。
三、实验 A:编译速度
A1. 五个配置 × 三次冷编译
# 默认(项目自带 lto=true, cgu=1, strip=true)cargo build --release --offline --target-dir tA
# 关掉 LTO,放开 codegen-unitscargo build --release --offline --target-dir tB \ --config profile.release.lto=false \ --config profile.release.codegen-units=16
# thin LTO + 放开 codegen-unitscargo build --release --offline --target-dir tC \ --config 'profile.release.lto="thin"' \ --config profile.release.codegen-units=16
# nightly 并行前端RUSTFLAGS=-Zthreads=16 cargo +nightly build --release --offline --target-dir tD
# 只检查类型,不产码cargo check --release --offline --target-dir tE原始数据(单位:秒):
| 配置 | 第 1 次 | 第 2 次 | 第 3 次 | 中位数 | 极差 | 相对默认 |
|---|---|---|---|---|---|---|
| stable 默认(fat LTO, cgu=1) | 41.62 | 42.44 | 41.66 | 41.66 | 0.82 | — |
stable lto=false, cgu=16 |
26.48 | 27.75 | 26.45 | 26.48 | 1.30 | −36.4% |
stable lto="thin", cgu=16 |
27.92 | 26.60 | 26.57 | 26.60 | 1.35 | −36.2% |
| nightly 默认(并行关闭) | 39.64 | 39.61 | 40.77 | 39.64 | 1.16 | −4.8%(跨版本) |
nightly -Zthreads=16 |
31.14 | 30.39 | 30.43 | 30.43 | 0.75 | −26.9%(跨版本) |
stable cargo check |
19.33 | 18.83 | 18.92 | 18.92 | 0.50 | −54.6% |
产物大小(三个版本各自 3 次完全一致,这是个很好的可复现性证据):
| 配置 | exe 字节数 | 可读 |
|---|---|---|
| 默认 | 2,884,096 | 2.75 MiB |
lto=false, cgu=16 |
3,146,752 | 3.00 MiB |
thin, cgu=16 |
3,253,760 | 3.10 MiB |
nightly -Zthreads=16 |
2,892,288 | 2.76 MiB |
配对结论(同类项对比):
- 同版本内,nightly 开并行前端 39.64s → 30.43s = −23.2%,这才是
-Zthreads的真实收益。 - stable 内,关掉 fat LTO 26.48s vs 默认 41.66s = −36.4%,代价是 exe 从 2.75 MiB 涨到 3.00 MiB(+9.1%)。
- thin LTO 与“完全关掉 LTO”几乎打平(26.60 vs 26.48,落在极差内),但产物反而大 3.4%(3.10 MiB vs 3.00 MiB)。也就是说在这个项目上,thin LTO 既没换来编译速度,也没换来体积,是纯亏的选项。这条与“thin 是速度与体积的良好折中”的流行说法相反,我按原始数据记录。
cargo check只花 18.92s,比完整 release 快 54.6%,足够过类型系统与借用检查。
A2. 热编译与“改一行”:release 没有增量
| 场景 | 第 1 次 | 第 2 次 | 第 3 次 | 中位数 |
|---|---|---|---|---|
| release,源码无改动,重复 build | 18.72 | 18.17 | 18.15 | 18.17 |
release,touch src/main.rs 后 build |
18.27 | 18.40 | 18.23 | 18.27 |
| dev,源码无改动,重复 build | 1.08 | 1.11 | 1.13 | 1.11 |
dev,touch src/main.rs 后 build |
1.17 | 2.00 | 1.19 | 1.19 |
「改一行」相对「什么都没改」的代价:
- release:+0.10s(+0.5%)
- dev:+0.08s(+7%)
release 的这两个数字在噪声内无法区分。含义很直接:release 构建没有“增量”这回事,每次都是全量重链,你改 main.rs 里的一个数字,和什么都不改,付出的时间一样多(约 18 秒)。dev 侧则是 1.1 秒级的真增量(其中一次 2.00s 是离群值,可能是杀软扫描新生成的 exe)。
dev 的产物是 7,168,000 字节(6.84 MiB),release 默认档是 2,884,096 字节(2.75 MiB),差 2.5 倍。
A3. 为什么 release 无法复用缓存:证据
用 -v 打开详细输出,在一个“什么都没改”的 release 构建上观察:
cargo build --release --offline --target-dir tA -v# ...# Running `...\tA\release\build\du-dust-c2b9546718135350\build-script-build`即使零改动,日志里依然出现 Running ...build-script-build。这是 Cargo 的保守行为:build.rs 如果没有输出 cargo:rerun-if-changed=... 声明自己的输入,Cargo 只好每次都重跑它。
于是链条是这样的:
build.rs每次重跑 → 触发下游重新判定;- 项目 profile 里有
lto = true+codegen-units = 1,最终二进制必须重新做跨 crate 优化并重新链接 → 依赖的.rlib虽然命中缓存,但最后一步省不掉。
两者叠加,得到 18 秒的“改一行”。这不是 Cargo 退化,是 LTO 的固有代价。想保留秒级迭代,选择只有两个:用 dev,或者把 release 的 lto 换成 thin/false。
A4. 链接器:rust-lld 在这个项目上零收益
rust-lld.exe 就在 sysroot 里,rustc 能按名字找到它,实测两种写法都能工作:
# 写法一:按名字(rustc 自己在 sysroot 里查)cargo build --release --offline --target-dir t-lld \ --config 'target.x86_64-pc-windows-msvc.linker="rust-lld"'
# 写法二:绝对路径cargo build --release --offline --target-dir tl2 \ --config "target.x86_64-pc-windows-msvc.linker=\"$(cygpath -m "$(rustc --print sysroot)")/lib/rustlib/x86_64-pc-windows-msvc/bin/rust-lld.exe\""结果:
| 配置 | 冷编译 | 产物 |
|---|---|---|
| 默认 profile | 41.66s(3 次中位) | 2,884,096 B |
| 默认 profile + rust-lld | 42.35s(单次) | 2,881,536 B |
差异 +1.7%,落在测量噪声里,没有收益。产物大小也对得上(差 2,560 字节,属于链接器版本差异)。
原因不难理解:这个项目只有一个最终二进制,74 个依赖全部以 .rlib 参与,几乎不调用系统链接器。rust-lld 的收益场景是“链接阶段占大头”的工程——大量 [[bin]]、大量测试目标、build.rs 生成多个可执行文件的项目。判断方法:看构建日志里链接阶段的占比,或者数一数项目里有多少个 bin/test 目标。
A5. nightly 并行前端:flag 名有坑
RUSTFLAGS="-Zthreads=16" cargo +nightly build --release收益已在上文配对给出:−23.2%。
但这里有个坑。在 1.100-nightly 上,-Z help 会提示该选项已改名:
-Z threads=val -- use `--jobs-frontend` instead照着提示写会直接构建失败,而且失败点很反直觉——不是编译报错,而是在探测阶段:
error: failed to run `rustc` to learn about target-specific information
Caused by: process didn't exit successfully: `rustc.exe - --crate-name ___ --print=file-names -Zjobs-frontend=16 --crate-type bin ...` (exit code: 1) --- stderr error: unknown unstable option: `jobs-frontend`正确写法只有两种:
RUSTFLAGS="-Zthreads=16" # 旧名仍可用,实测有效RUSTFLAGS="-Zunstable-options --jobs-frontend=16" # 新名,但必须同时给 -Zunstable-options另需注意:并行前端是单个 crate 内部的前端工作并行,而 Cargo 本来就会并行编译多个 crate。所以在“依赖多、单个 crate 小”的项目上收益有限,收益最大的是“依赖很少但单个 crate 巨大”的项目。
A6. Cranelift 后端:快,但产物不可接受
rustup component add rustc-codegen-cranelift --toolchain nightlyRUSTFLAGS="-Zcodegen-backend=cranelift" cargo +nightly build --release --offline \ --target-dir t-cranelift \ --config profile.release.lto=false \ --config profile.release.codegen-units=16| 配置 | 冷编译 | 产物 |
|---|---|---|
nightly + LLVM(lto=off, cgu=16) |
26.48s(3 次中位) | 3,146,752 B(3.00 MiB) |
| nightly + Cranelift(同配置) | 23.68s(单次) | 8,399,360 B(8.01 MiB) |
编译快 10.6%(单次测量,仅供参考),产物体积 2.7 倍。
产物做了功能验证(这点很重要,因为“能编译”不等于“能跑”):
cmd.exe /c "t-cranelift\release\dust.exe --version"# Dust 1.2.6cmd.exe /c "t-cranelift\release\dust.exe -n 1 src"# 128Ki ┌─┴ src ...能正常工作。但 8 MiB 的二进制换 10% 编译时间,只适合 CI 里的快速编译校验,不适合出包。
A7. 一个环境坑:MSYS bash 无法直接执行产物
实测中我发现无法用相对或绝对路径执行刚编出来的 exe:
./t-base/release/dust.exe --version# error: command not found: ./t-base/release/dust.exe (rc=127)
/c/Users/.../t-base/release/dust.exe --version# error: command not found (rc=127)但走 PATH 或 cmd.exe 就正常:
PATH="$PWD/t-base/release:$PATH" dust.exe --version# Dust 1.2.6
cmd.exe /c "t-base\release\dust.exe --version"# Dust 1.2.6文件本身是合法的 PE32+ executable ... x86-64(file 确认),所以这不是产物问题,而是这套 MSYS 环境对带路径的 exe 执行有限制。如果你复现时遇到同样的 rc=127,别怀疑编译,换 PATH 或 cmd.exe 调用即可。
四、实验 B:exe 体积的逐项消融
B1. 设计
真实项目的 release profile 是一整套开关,无法回答“每个开关各值多少”。所以另建一个零依赖的单 crate,用多个继承自 release 的 profile,每次只动一个变量:
[package]name = "size-bench"version = "0.1.0"edition = "2021"
[profile.release] # 默认:opt-level 3, cgu 16, 无 LTO, unwind, 不 strip
[profile.p-lto] # 只加 LTOinherits = "release"lto = "fat"
[profile.p-cgu] # 只加 codegen-units=1inherits = "release"codegen-units = 1
[profile.p-abort] # 只加 panic=abortinherits = "release"panic = "abort"
[profile.p-strip] # 只加 stripinherits = "release"strip = "symbols"
[profile.p-full] # 四项全上inherits = "release"lto = "fat"codegen-units = 1panic = "abort"strip = "symbols"
[profile.p-nostrip] # 与 p-full 相同但 strip=none(用于隔离 strip)inherits = "release"lto = "fat"codegen-units = 1panic = "abort"strip = "none"
[profile.p-sizez] # 全套 + opt-level=zinherits = "release"opt-level = "z"lto = "fat"codegen-units = 1panic = "abort"strip = "symbols"
[profile.p-sizez-dbg] # 全套 + opt-level=z + debug=1inherits = "release"opt-level = "z"lto = "fat"codegen-units = 1panic = "abort"strip = "none"debug = 1被测代码是纯 std 的字符串/哈希表/格式化小程序(约 60 行),刻意包含格式化、HashMap、panic! 路径和 #[inline(never)] 函数,让各优化等级有可区分的产物。
构建与测量:
for p in release p-lto p-cgu p-abort p-strip p-full p-nostrip p-sizez p-sizez-dbg; do cargo build --profile "$p" stat -c '%s %n' "target/$p/size-bench.exe" stat -c '%s %n' "target/$p/size_bench.pdb" 2>/dev/nulldoneB2. 结果
| profile | exe(字节) | exe(KiB) | 相对默认 | 该档编译耗时 | pdb(字节) |
|---|---|---|---|---|---|
| release 默认 | 156,672 | 153.0 | — | 0.49s | 1,331,200 |
+ lto = "fat" |
143,360 | 140.0 | −8.5% | 1.33s | — |
+ codegen-units = 1 |
155,136 | 151.5 | −1.0% | 0.54s | — |
+ panic = "abort" |
151,040 | 147.5 | −3.6% | 0.44s | — |
+ strip = "symbols" |
156,672 | 153.0 | 0.0% | 0.45s | — |
四项全上(p-full) |
135,168 | 132.0 | −13.7% | 1.39s | 897,024 |
四项全上但 strip="none"(p-nostrip) |
135,168 | 132.0 | −13.7% | 1.40s | 897,024 |
全套 + opt-level = "z"(p-sizez) |
128,000 | 125.0 | −18.3% | 1.26s | 888,832 |
全套 + opt-level="z" + debug=1 |
128,512 | 125.5 | −18.0% | 1.30s | 987,136 |
性价比排序:lto(−8.5%)> panic=abort(−3.6%)> codegen-units=1(−1.0%)> strip(0.0%)。再加上 opt-level="z" 才能把总收益推到 −18.3%。
代价用编译时间衡量:默认档 0.49s,上了 LTO 后 1.33~1.40s,2.7 倍。小 crate 上不明显,真实项目里这就是 A1 表里那 15 秒的差距。
B3. 重点:Windows 上 strip 是无效操作
这一项我做了最严格的对照——两个 profile 除 strip 外完全相同(都是 lto=fat, codegen-units=1, panic=abort):
| 配置 | exe | pdb |
|---|---|---|
strip = "symbols"(p-full) |
135,168 B | 897,024 B |
strip = "none"(p-nostrip) |
135,168 B | 897,024 B |
字节数完全相同,连 .pdb 都没变。
原因:MSVC 目标下的调试信息与符号由 link.exe 决定,写进独立的 .pdb 文件,而 rustc 的 strip 走 LLVM objcopy 路径。面对 PE/COFF 产物,绝大多数符号本来就不在 exe 里,strip 没有东西可剥。
所以你不需要为 strip 反复调参。这条结论只适用于 *-pc-windows-msvc;Linux ELF 和 macOS Mach-O 上 strip 是真实有效的(符号表内嵌在可执行文件里),别把 Windows 的结论外推。
B4. .pdb 才是体积大头
| 产物 | exe | pdb | 比例 |
|---|---|---|---|
| 小 crate,release 默认 | 153.0 KiB | 1.27 MiB | 8.5× |
| 小 crate,四项全上 | 132.0 KiB | 876 KiB | 6.6× |
小 crate,opt-level="z" 全套 |
125.0 KiB | 868 KiB | 6.9× |
小 crate,debug=1 不 strip |
125.5 KiB | 964 KiB | 7.7× |
| du-dust,默认 profile | 2.75 MiB | 1.50 MiB | 0.55× |
在 exe 上抠 8.5% 的同时,旁边躺着一个比它大 6~8 倍的 .pdb。发布时把 .pdb 单独归档、不随分发包发出去,收益远大于继续调优化等级。
顺带一个反直觉点:debug = 1 在这一档里几乎没让 exe 变大(125.5 vs 125.0 KiB,+0.4%),但 pdb 从 868 KiB 涨到 964 KiB。Windows 的调试信息进 pdb 不进 exe,所以“加调试信息会不会让发布体积变大”这个担心,在 MSVC 上基本不成立。
五、实验 C:Cargo 缓存勘察
du -sh ~/.cargo/registry/cache ~/.cargo/registry/src ~/.cargo/registry/index ~/.cargo/git ~/.cargo/bindu -sh ~/.rustupdu -sh "$TEMP"/proc-macro-srv*| 位置 | 实测大小 | 删掉的代价 |
|---|---|---|
项目 target/(本实验各档) |
217 MB ~ 677 MB 每档 | 全量重编 |
~/.cargo/registry/src |
642 MB | 重新解压已下载的包 |
~/.cargo/registry/cache |
281 MB | 重新下载 .crate |
~/.cargo/registry/index |
73 MB | 重新拉索引 |
~/.cargo/git |
17 MB | 重新 clone git 依赖 |
~/.cargo/bin |
20 MB | cargo install 装的东西全没 |
~/.rustup(stable + nightly) |
3.2 GB | 重新下载工具链(各 1.6 GB) |
%TEMP%\proc-macro-srv* |
794 MB / 10 份 | 无需代价,纯残留 |
各档 target 目录的实际占用(这也是为什么实验后要清理):
| 目录 | 大小 |
|---|---|
| t-dev(dev profile) | 677 MB |
| t-thin | 442 MB |
| t-fast / t-raw | 440 MB |
| t-base / t-lld | 435 MB |
| t-check | 217 MB |
(cargo check 的目录只有 217 MB,不到完整 build 的一半。)
%TEMP% 下的 proc-macro-srv* 值得单独说:
23 MB proc-macro-srv5b44-0 172 MB proc-macro-srv74cc-0 111 MB proc-macro-srv754c-0 172 MB proc-macro-srv77e0-0 111 MB proc-macro-srv77e0-1 23 MB proc-macro-srv7dec-0 69 MB proc-macro-srv8410-0 23 MB proc-macro-srva90c-0 69 MB proc-macro-srvaf88-0 23 MB proc-macro-srvb998-0共 10 个目录、约 794 MB。这是 rust-analyzer 为每个工具链解压的 proc-macro server 副本,进程退出后不清理,日积月累。确认没有 rust-analyzer 在跑后可以直接删:
rm -rf "$TEMP"/proc-macro-srv*清理命令清单:
cargo clean # 清当前项目 target(通常是最大头)cargo cache -a # cargo-cache 插件,清 registry 里的陈旧版本cargo cache --autoclean # 自动清理rm -rf ~/.cargo/registry/src # 清源码缓存,下次自动重新解压rm -rf "$TEMP"/proc-macro-srv* # 794 MB 残留另外这台机器没有 ~/.cargo/config.toml,全局配置为空。跨项目、跨机器的产物级缓存要靠 sccache(cargo install sccache + RUSTC_WRAPPER=sccache),这部分我没有实测,不写结论。
六、结论与证据索引
| # | 结论 | 支撑数据 |
|---|---|---|
| 1 | 日常用 dev 迭代,别用 release |
dev 改一行 1.19s vs release 改一行 18.27s |
| 2 | release 没有增量编译 | release 无改动 18.17s,改一行 18.27s,差 0.1s |
| 3 | 根因是 build script 每次重跑 + LTO 全量重链 | -v 日志出现 Running ...build-script-build |
| 4 | cargo check 是第二便宜的手段 |
18.92s,比 release build 快 54.6% |
| 5 | lto 是编译时间的最大变量,也是体积的最大杠杆 |
关掉 LTO 省 36.4% 时间,代价 exe +9.1% |
| 6 | thin LTO 在此项目上双输 | 26.60s(≈关闭 LTO 的 26.48s),但体积大 3.4% |
| 7 | strip 在 Windows/MSVC 上完全无效 |
strip 开/关:exe 均 135,168 B,pdb 均 897,024 B |
| 8 | .pdb 比 exe 大 6~8 倍,应单独归档 |
小 crate:132 KiB exe / 876 KiB pdb |
| 9 | opt-level="z" 是体积的最后一档 |
−18.3%(全套基础上再降) |
| 10 | rust-lld 对单二进制项目零收益 |
42.35s vs 41.66s(噪声内) |
| 11 | 单二进制项目用不到 lld 的原因:无链接阶段 | 74 个依赖全为 .rlib |
| 12 | nightly 并行前端真实收益 −23.2% | 同版本配对:39.64s → 30.43s |
| 13 | -Zjobs-frontend=N 会直接构建失败 |
error: unknown unstable option: jobs-frontend |
| 14 | Cranelift 编译快 10.6%,产物 2.7 倍 | 23.68s / 8.01 MiB vs 26.48s / 3.00 MiB |
| 15 | panic="abort" 体积收益 3.6% |
147.5 KiB vs 153.0 KiB |
| 16 | codegen-units=1 体积收益仅 1.0% |
151.5 KiB vs 153.0 KiB |
| 17 | 缓存占用最大的是 .rustup 与 registry/src |
3.2 GB / 642 MB |
| 18 | %TEMP% 有 794 MB rust-analyzer 残留 |
10 个 proc-macro-srv* 目录 |
| 19 | MSYS 下必须走 PATH 或 cmd.exe 执行产物 | 直接调用 rc=127,走 PATH 输出 Dust 1.2.6 |
七、局限
诚实地列出这份报告的外推边界:
- 单机单项目。 只有一台 16 核机器、一个真实项目。各档的百分比与项目规模、依赖数量、单 crate 体量强相关,别把「−36.4%」「−23.2%」当成通用常数。
- 每档 3 次,未做统计检验。 报的是中位数与极差,用来观察稳定性够用,但不构成显著性结论。极差在 0.5~1.35s,与部分配置间的差距(如 thin 26.60 vs lto=off 26.48)同量级——这几档之间的差异不能被判定为真实差异,我据实写出而不做强结论。
- 跨版本比较已单独标注。 nightly 1.100 与 stable 1.98 的差异(约 −4.8%)混在“版本升级”里,只有同版本配对数据用于归因。
rust-lld与 Cranelift 是单次测量。 这两项没做 3 次重复,报告里已注明,它们的差额(+1.7% 与 −10.6%)比重复测量的噪声更接近临界。- 体积实验用的是小 crate。 小 crate 的符号/对齐占比与大项目不同,绝对 KiB 数不可直接套用,但开关之间的相对贡献(lto ≫ abort > cgu > strip=0)具有参考价值。
strip的结论仅限 MSVC。 ELF/Mach-O 上结论会不同。- 未测项:
sccache、-Zbuild-std、mold链接器(Linux)、debug = "line-tables-only"、增量编译在 release 下强制开启(incremental = true)的效果。这些都可能显著改变结论,本报告不涉及。
八、复现脚本
#!/usr/bin/env bash# 复现本报告的编译速度实验(需要网络或用 --offline + 本地 registry 缓存)set -uexport CARGO_TERM_COLOR=never
# 1. 准备源码副本cd /path/to/workdircp -r ~/.cargo/registry/src/index.crates.io-*/du-dust-1.2.6 dust2cd dust2 && rm -f Cargo.lock
# 2. 工具函数:从 Cargo 输出里取耗时(Cargo 输出 "Finished ... in X.XXs")tm() { grep -o "in [0-9.]*s" /tmp/r.log | sed -n '$p' | grep -o "[0-9.]*"; }
# 3. 单档测量:删掉 target 目录保证冷编译one() { local label="$1" tdir="$2"; shift 2 rm -rf "$tdir" "$@" > /tmp/r.log 2>&1 local rc=$? local secs; secs=$(tm) local exe; exe=$(stat -c %s "$tdir/release/dust.exe" 2>/dev/null || echo 0) echo "$label | ${secs}s | rc=$rc | exe=${exe}B"}
# 4. 五个配置 × 3 次for i in 1 2 3; do echo "=== round $i ===" one "default " tA cargo build --release --offline --target-dir tA one "lto=off cgu16 " tB cargo build --release --offline --target-dir tB \ --config profile.release.lto=false --config profile.release.codegen-units=16 one "thin cgu16 " tC cargo build --release --offline --target-dir tC \ --config 'profile.release.lto="thin"' --config profile.release.codegen-units=16 one "nightly thr16 " tD env RUSTFLAGS=-Zthreads=16 cargo +nightly build --release --offline --target-dir tD one "cargo check " tE cargo check --release --offline --target-dir tEdone
# 5. 热编译 vs 改一行for i in 1 2 3; do one "release warm #$i" tA cargo build --release --offline --target-dir tA; donefor i in 1 2 3; do touch src/main.rs; one "release touch #$i" tA cargo build --release --offline --target-dir tA; done
# 6. dev 侧对照rm -rf tDevone "dev cold" tDev cargo build --offline --target-dir tDevfor i in 1 2 3; do one "dev warm #$i" tDev cargo build --offline --target-dir tDev; donefor i in 1 2 3; do touch src/main.rs; one "dev touch #$i" tDev cargo build --offline --target-dir tDev; done九、可以直接抄的配置
~/.cargo/config.toml:
[target.x86_64-pc-windows-msvc]# 实测对单二进制项目无收益(42.35s vs 41.66s),# 多 bin / 大量测试目标的项目才值得开# linker = "rust-lld"
[net]retry = 3git-fetch-with-cli = true项目 Cargo.toml:
[profile.dev]debug = 1 # 默认 2;降到 1 能减小 target/ 并加快链接,断点仍可用
[profile.release]opt-level = "z" # 体积优先(实测 −18.3%);要运行速度就写 3lto = "fat" # 追求最小体积。想省编译时间就用 false(−36.4%), # 不建议 "thin":本项目上它与 false 打平但产物更大codegen-units = 1panic = "abort" # −3.6%;会禁用 catch_unwind,测试 profile 别继承# strip = "symbols" # Windows 上无效;Linux/macOS 上保留
[profile.release.package."*"]opt-level = 2 # 依赖不必做极致优化,省下大量编译时间仅 nightly 可用的一条(实测 −23.2%):
[build]rustflags = ["-Zthreads=16"] # stable 上会直接报错,别写进全局配置panic = "abort" 会改变 cargo test 的 panic 行为(无法捕获展开),若测试也走 release profile,务必确认后再开。