改完这行 Cargo.toml,我的构建从 47 秒降到 6 秒

项目是 workspace,Rust 1.78,Linux 上跑。现象很稳定:一次性 cargo build --workspace 要 47 秒,改一个只依赖 serde 的小 crate 里的函数体,再构建又是 40 秒出头。用 cargo build --timings 导出 HTML 看了一眼,主要时间不在我的代码上,而是十几个 proc-macro crate 反复被编译:syn、quote、proc-macro2 每次都在里面。

做法只有两步。先在 workspace 根 Cargo.toml 里加 resolver 版本,再统一几个跨 crate 共用的依赖:

[workspace]
resolver = "2"
members = ["core", "api", "cli"]

第二步是确认 proc-macro 的构建产物真的被复用,不要每个成员各写一份 [profile.dev]:

[profile.dev]
incremental = true
[profile.dev.build-override]
opt-level = 0

改完第一次全量构建还是要 40 多秒,但那次之后,只改业务 crate 的构建落到了 6 秒左右,--timings 里 proc-macro 那一排基本不再重新出现。

机制上:proc-macro 和 build script 是一个独立的构建单元图,resolver = "2" 才让 workspace 里的 feature 在编译期和运行期分开解析,避免不同成员的 feature 合并导致上游 crate 反复变体;build-override 则保证这些工具的优化级别不会跟着 dev profile 的调试信息一起抖动。代价是首次构建会多一次 proc-macro 的编译,以及 resolver 2 会暴露一些原本被合并 feature 掩盖的编译错误,这些错误通常说明你的 feature 声明本来就有问题,修起来会花点时间。

失效的地方:如果 workspace 里有 [patch] 或 [replace] 指到本地路径的依赖,缓存键会随路径内容变,仍是每次重编;另外 incremental = true 在 CI 上基本没收益,CI 里更稳的是固定 CARGO_TARGET_DIR 加 sccache,否则多机并行反而更慢。我自己踩过一次:在成员 crate 里写了 [profile.dev],cargo 直接忽略并只给 warning,一度以为改动没生效。

你们那边改一个叶子 crate,第二次构建一般要多久?有没有遇到 proc-macro 每次都重编的情况,最后是怎么定位的?

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

0 条评论

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

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