Cargo configuration options and source code organization patterns can help improve build performance, by prioritizing it over other aspects which may not be as important for your circumstances.
Same as when optimizing runtime performance, be sure to measure these changes against the workflows you actually care about, as we provide general guidelines and your circumstances may be different, it is possible that some of these approaches might actually make build performance worse for your use-case.
Example workflows to consider include:
cargo check after making a code change)cargo test after making a code change)Cargo uses configuration defaults that try to balance several aspects, including debuggability, runtime performance, build performance, binary size and others. This section describes several approaches for changing these defaults that should be designed to maximize build performance.
Common locations to override defaults are:
Cargo.toml manifest$WORKSPACE_ROOT/.cargo/config.toml configuration fileCargo.toml, this is sensitive to what directory you invoke cargo from (see #2930)$CARGO_HOME/.cargo/config.toml configuration fileRecommendation: Add to your Cargo.toml or .cargo/config.toml:
[profile.dev] debug = "line-tables-only" [profile.dev.package."*"] debug = false [profile.debugging] inherits = "dev" debug = true
This will:
dev profile (default for development commands) to:--profile debuggingNote: re-evaluating the
devprofile is being tracked in #15931.
Trade-offs:
cargo build)target directoryRecommendation:
$ rustup component add rustc-codegen-cranelift-preview --toolchain nightly
Cargo.toml or .cargo/config.toml:[profile.dev] codegen-backend = "cranelift"
-Z codegen-backend or enable the codegen-backend feature in .cargo/config.toml.This will change the dev profile to use the Cranelift codegen backend for generating machine code, instead of the default LLVM backend. The Cranelift backend should generate code faster than LLVM, which should result in improved build performance.
Trade-offs:
cargo build)cargo test, but might increase its test execution partRecommendation: Add to your .cargo/config.toml:
[build] rustflags = "-Zthreads=8"
This rustflags will enable the parallel frontend of the Rust compiler, and tell it to use n threads. The value of n should be chosen according to the number of cores available on your system, although there are diminishing returns. We recommend using at most 8 threads.
Trade-offs:
cargo check and cargo build)Consider: installing and configuring an alternative linker, like LLD, mold or wild. For example, to configure mold on Linux, you can add to your .cargo/config.toml:
[target.'cfg(target_os = "linux")'] # mold, if you have GCC 12+ rustflags = ["-C", "link-arg=-fuse-ld=mold"] # mold, otherwise linker = "clang" rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
While dependencies may be built in parallel, linking all of your dependencies happens at once at the end of your build, which can make linking dominate your build times, especially for incremental rebuilds. Often, the linker Rust uses is already fairly fast and the gains from switching may not be worth it, but it is not always the case. For example, Linux targets besides x86_64-unknown-linux-gnu still use the Linux system linker which is quite slow (see rust#39915 for more details).
Trade-offs:
Consider: adding to your project's .cargo/config.toml
[resolver] feature-unification = "workspace"
When invoking cargo, features get activated based on which workspace members you have selected. However, when contributing to an application, you may need to build and test various packages within the application, which can cause extraneous rebuilds because different sets of features may be activated for common dependencies. With feature-unification, you can reuse more dependency builds by ensuring the same set of dependency features are activated, independent of which package you are currently building and testing.
Trade-offs:
--workspace doesn‘t work for you, then this won’t eitherRecommendation: periodically review unused dependencies for removal using:
$ cargo +nightly check -Zcargo-lints --workspace --all-targets
This may have false positives from:
build.rs or RUSTFLAGSAlso, periodically review hidden cargo::unused_dependencies results:
$ CARGO_LOG=cargo::diagnostics::rules::unused_dependencies=debug cargo +nightly check -Zcargo-lints --workspace --all-targets
This will show potential unused dependencies for
package.rust-version is too old to use [lints.cargo][target."cfg(false)".dependencies])[dev-dependencies] as there is not a way yet to ensure all consumers of these are builtWhen changing code, it can be easy to miss that a dependency is no longer used and can be removed.
Trade-offs:
Recommendation: Periodically review unused features from dependencies for removal using third-party tools like cargo-features-manager, cargo-unused-features.
When changing code, it can be easy to miss that a dependency's feature is no longer used and can be removed. This can reduce the number of transitive dependencies being built or reduce the amount of code within a crate being built. When removing features, extra caution is needed because features may also be used for desired behavior or performance changes which may not always be obvious from compiling or testing.
Trade-offs: