用 cargo tree 揪出重复依赖导致编译变慢

项目是 Rust 1.76,Linux x86_64,workspace 里 14 个 crate,cargo build 冷编译要 3 分 40 秒。症状是改一行业务代码,重新编译也要 40 多秒,而改动本身只碰一个叶子 crate。

我先没急着上 sccache 之类的工具,而是看依赖图。

cargo tree -d --workspace
cargo tree -d --workspace --edges normal,build

-d 只列重复依赖。输出里 syn 有 1.0 和 2.0 两份,hashbrown 有 0.12、0.14 两份,bitflags 有 1.x 和 2.x。数量不算离谱,但 syn 两份意味着 proc-macro 编译了两遍,hashbrown 两份意味着两套泛型实例化。

接着定位是谁拉进来的:

cargo tree -i syn@1.0.207 --workspace
cargo tree -i hashbrown@0.12.3 --workspace

syn 1.x 来自一个老版本的 derive 库,hashbrown 0.12 来自一个间接依赖的 indexmap 老版本。这两个库在我们自己的代码里根本没直接用到,只是被传递依赖带进来。

做法是先升直接依赖,让传递依赖自然收敛:

cargo update -p indexmap --precise 2.7.0
cargo update -p <那个derive库> --precise <新版本>

升级后 cargo tree -d 里 syn 和 hashbrown 都只剩一份。冷编译从 3 分 40 秒降到 2 分 55 秒,增量编译从 40 多秒降到 28 秒左右。这个收益不算戏剧性,但它是零成本的,不需要改构建脚本。

为什么有效:Cargo 的构建单元以 crate 加 feature 组合为单位,同一个库的两个大版本会被当成两个完全独立的编译目标,proc-macro 还要额外在 host 上编译一次。增量编译时,依赖图里任何一个上游单元失效都会触发下游重编,重复依赖让这个失效链变长。

哪里会失效:cargo update --precise 只是在 lockfile 上动,如果上游库的版本约束根本不允许新版本,这条命令会失败,得先升那个直接依赖。另外 cargo tree -d 不显示 feature 层面的重复,两个 crate 用同一个 serde 版本但 feature 集合不同,一样会被编两遍,这时要看 cargo tree -e features -i serde。还有 workspace 里不同成员用不同 resolver 行为也会绕开统一,我们后来在根 Cargo.toml 里显式写了 resolver = "2"。

你们项目里 cargo tree -d 一般会列出几组重复?有没有那种升不动、只能长期共存的大版本?

0
赞同度 100% · 0 条评论 · 5 阅读

0 条评论

支持 Markdown · 回复会实时推送给对方

还没有评论,写下第一条。