第18章 Cargo 配置:Profile 与编译优化详解
Profiles 提供了一种调整编译器设置的方式,从而优化或影响调试符号等特性。
Cargo 内置了 4 种 profile:dev、release、test 和 bench。如果未在命令行指定 profile,系统会根据当前运行的命令自动选择。除了这些内置 profile,用户还可以自定义特定的 profile。
可以通过 Cargo.toml 中的 [profile] 表格来修改 profile 设置。在每个命名的 profile 中,可以通过键值对来修改具体设置,例如:
[profile.dev]
opt-level = 1 # 使用稍好的优化。
overflow-checks = false # 禁用整数溢出检查。
Cargo 仅读取工作区根目录 Cargo.toml 清单中的 profile 设置。依赖项中定义的 profile 设置将被忽略。
此外,还可以通过 config 定义来覆盖 profile。在配置文件或环境变量中指定 profile 将覆盖来自 Cargo.toml 的设置。
Profile 设置
以下是 profile 中可以控制的设置列表。
opt-level
opt-level 设置控制 -C opt-level 标志,决定优化级别。较高的优化级别可能会生成运行更快的代码,但代价是编译时间更长。高级别优化还可能改变和重排编译后的代码,使其更难配合调试器使用。
有效选项包括:
0:不优化1:基础优化2:部分优化3:全面优化"s":针对二进制文件大小优化"z":针对二进制文件大小优化,同时关闭循环向量化。
建议尝试不同的优化级别,以找到适合项目的最佳平衡点。可能会出现一些反直觉的结果,例如 3 级比 2 级更慢,或者 "s" 和 "z" 级别生成的体积并不一定更小。随着 rustc 新版本改变优化行为,你还需要适时重新评估当前的配置。
如需了解更高级的优化技术,请参阅 基于性能的优化(PGO)。
debug
debug 配置项用于控制 -C debuginfo 标志,该标志决定了编译后二进制文件中包含的调试信息数量。
有效选项如下:
0、false或"none":不包含任何调试信息,release模式的默认设置"line-directives-only":仅包含行指令信息。对于 nvptx* 目标架构,这会启用 性能剖析(profiling)。其他用途下,line-tables-only是兼容性更好的选择"line-tables-only":仅包含行表信息。生成最少的调试信息以支持带文件名和行号的回溯,但不包含变量或函数参数等信息1或"limited":不包含类型或变量级信息的调试信息。比line-tables-only生成更详细的模块级信息2、true或"full":完整的调试信息,dev模式的默认设置
有关各选项的具体作用,请查阅 rustc 关于 debuginfo 的文档。
根据你的需求,可能还需要配置 split-debuginfo 选项。
MSRV:
none、limited、full、line-directives-only和line-tables-only需要 Rust 1.71 或更高版本
split-debuginfo
split-debuginfo 设置对应 -C split-debuginfo 标志,用于控制生成的调试信息是放在可执行文件内部,还是放在它旁边的文件中。
该选项是字符串类型,可选值与编译器接受的值相同。对于启用了调试信息的 profile,macOS 上默认值为 unpacked;其他情况下默认值由 rustc 文档规定,并因平台而异。某些选项仅在 nightly 渠道可用。等测试更充分、DWARF 支持稳定后,Cargo 的默认值将来可能会调整。
注意,Cargo 和 rustc 对这个选项的默认值不同。这个选项的存在是为了让 Cargo 尝试不同的标志组合,以提供更好的调试体验和开发者体验。
strip
strip 选项对应 -C strip 标志,指示 rustc 从二进制文件中剥离符号或调试信息。启用方式如下:
[package]
# ...
[profile.release]
strip = "debuginfo"
strip 的可选字符串值为 "none"、"debuginfo" 和 "symbols",默认值是 "none"。
也可以用布尔值 true 或 false 来配置:strip = true 等价于 strip = "symbols";strip = false 等价于 strip = "none",即完全关闭 strip。
debug-assertions
debug-assertions 配置项控制 -C debug-assertions 标志,用于开启或关闭 cfg(debug_assertions) 条件编译。调试断言旨在包含仅在调试或开发构建中可用的运行时验证,这些验证在发布构建中通常因开销过大或不希望存在而被排除。调试断言会启用标准库中的 debug_assert! 宏。
有效的选项包括:
true:启用false:禁用
overflow-checks
overflow-checks 配置项控制 -C overflow-checks 标志,该标志决定了运行时整数溢出的行为。启用溢出检查后,发生溢出时会触发 panic。
有效的选项包括:
true:启用false:禁用
lto
lto 配置项控制 rustc 的 -C lto、-C linker-plugin-lto 以及 -C embed-bitcode 选项,这些选项用于控制 LLVM 的链接时优化。LTO 可以利用全程序分析生成优化更好的代码,代价是链接时间更长。
有效的选项包括:
true或"fat":执行“fat” LTO,尝试在依赖图中的所有 crate 之间执行优化。"thin":执行“thin” LTO。与“fat”模式类似,但执行时间大幅缩短,性能收益却与“fat”模式相近。false:执行“thin local LTO”,即仅对本地 crate 的多个codegen units执行“thin” LTO。若 codegen units 为 1 或opt-level为 0,则不执行 LTO。"off":禁用 LTO。
若对跨语言 LTO 感兴趣,请查阅linker-plugin-lto 章节。Cargo 目前尚不原生支持该功能,但可通过RUSTFLAGS实现。
panic
panic 设置用于控制-C panic 标志,该标志决定使用的 panic 策略。
可选值包括:
"unwind":发生 panic 时展开栈(unwind the stack)。"abort":发生 panic 时终止进程。
设置为"unwind"时,实际行为取决于目标平台的默认配置。例如,NVPTX 平台不支持栈展开,因此始终使用"abort"。
测试、基准测试、构建脚本和 proc macros 会忽略panic设置。rustc 测试框架当前要求使用unwind行为。关于启用abort行为的panic-abort-tests 不稳定标志,详见panic-abort-tests。
此外,若使用abort策略并构建测试,所有依赖项也将被强制使用unwind策略进行构建。
incremental
incremental 设置用于控制-C incremental 标志,该标志决定是否启用增量编译。增量编译会使rustc将额外信息写入磁盘,供重新编译该 crate 时复用,从而加快重编译速度。这些额外信息存储在target 目录中。
可选值包括:
true:启用false: 禁用
增量编译只对 workspace 成员和 “path” 依赖生效。
可以通过 CARGO_INCREMENTAL 环境变量 或 build.incremental 配置项在全局覆盖 incremental 的值。
codegen-units
codegen-units 设置对应 -C codegen-units 标志,控制一个 crate 会被拆分成多少个 “代码生成单元”。代码生成单元越多,crate 就能有更多部分并行处理,可能缩短编译时间,但生成的代码可能更慢。
此选项接受一个大于 0 的整数。
增量构建时默认为 256,非增量构建时默认为 16。
rpath
rpath 设置对应 -C rpath 标志,控制是否启用 rpath。
默认 profile
dev
dev profile 用于日常开发和调试,是 cargo build 等构建命令的默认选择,cargo install --debug 也使用它。
dev profile 的默认设置如下:
[profile.dev]
opt-level = 0
debug = true
split-debuginfo = '...' # 具体值取决于平台。
strip = "none"
debug-assertions = true
overflow-checks = true
lto = false
panic = 'unwind'
incremental = true
codegen-units = 256
rpath = false
release
release profile 用于生成经过优化的发布版制品。使用 --release 标志时会启用该 profile,它也是 cargo install 的默认选择。
release profile 的默认设置如下:
[profile.release]
opt-level = 3
debug = false
split-debuginfo = '...' # Platform-specific.
strip = "none"
debug-assertions = false
overflow-checks = false
lto = false
panic = 'unwind'
incremental = false
codegen-units = 16
rpath = false
test
test profile 是 cargo test 使用的默认 profile。
test profile 继承自 dev profile 的设置。
bench
bench profile 是 cargo bench 使用的默认 profile。
bench profile 继承自 release profile 的设置。
Build Dependencies
为了加快编译速度,默认情况下所有 profile 都不会优化构建依赖(构建脚本、proc macros 及其依赖项),并且在构建依赖项未被用作运行时依赖项时,避免计算 debug 信息。构建覆盖(build overrides)的默认设置如下:
[profile.dev.build-override]
opt-level = 0
codegen-units = 256
debug = false # when possible
[profile.release.build-override]
opt-level = 0
codegen-units = 256
但是,如果在运行构建依赖项时发生错误,开启完整的 debug 信息将有助于在需要时改进回溯(backtrace)和调试体验:
debug = true
除此之外,构建依赖项会继承当前使用的活动 profile 的设置,具体说明见Profile 选择。
Custom profiles
除了内置的 profile,还可以定义额外的自定义 profile。这有助于建立多种工作流和构建模式。定义自定义 profile 时,必须指定 inherits 键,以说明当未指定某项设置时,该自定义 profile 继承哪个 profile 的设置。
例如,假设你想对比普通的 release 构建与带有 LTO 优化的 release 构建,可以在 Cargo.toml 中指定如下内容:
[profile.release-lto]
inherits = "release"
lto = true
然后可以使用 --profile 参数来选择此自定义 profile:
cargo build --profile release-lto
每个 Profile 的构建产物会存放在 target 目录 下与其同名的子目录中。例如,上述示例的产物会输出到 target/release-lto 目录。
Profile 选择
实际使用的 Profile 取决于命令、命令行参数(如 --release 或 --profile)以及包(在涉及覆盖设置时)。若未指定,默认 Profile 如下:
| 命令 | 默认 Profile |
|---|---|
cargo run、cargo build、cargo check、cargo rustc | dev Profile |
cargo test | test Profile |
cargo bench | bench Profile |
cargo install | release Profile |
使用 --profile=NAME 选项可切换到指定名称的 Profile。--release 参数等价于 --profile=release。
选定的 Profile 适用于所有 Cargo 目标,包括库、二进制文件、示例、测试和基准测试。
如需为特定包指定 Profile,可使用下文介绍的覆盖设置。
覆盖设置
可以为特定包和构建时的 crate 覆盖 Profile 配置。若要修改指定包的设置,请通过 package 表进行配置:
# `foo` 包将使用 -Copt-level=3 标志。
[profile.dev.package.foo]
opt-level = 3
包名其实是一个 Package ID Spec,所以可以用类似 [profile.dev.package."foo:2.1.0"] 的语法来针对某个包的特定版本进行设置。
如果要覆盖所有依赖项的设置(但不包括 workspace 成员),可以使用 "*" 作为包名:
# 为依赖项设置默认值。
[profile.dev.package."*"]
opt-level = 2
如果要覆盖构建脚本、proc macro 及其依赖项的设置,可以使用 build-override 表:
# 为构建脚本和 proc-macro 设置参数。
[profile.dev.build-override]
opt-level = 3
注意:如果某个依赖项既是普通依赖又是构建依赖,在不指定
--target的情况下,Cargo 会尽量只构建一次。但使用build-override时,该依赖项可能需要构建两次——一次按普通依赖构建,一次按覆盖后的构建设置构建。这可能会增加首次构建的时间。
各设置的生效优先级按以下顺序决定(先匹配者优先):
[profile.dev.package.name]— 指定名称的包。[profile.dev.package."*"]— 所有非 workspace 成员。[profile.dev.build-override]— 仅用于构建脚本、proc macro 及其依赖项。[profile.dev]—Cargo.toml中的设置。- Cargo 内置的默认值。
覆盖配置不能指定 panic、lto 或 rpath 设置。
覆盖与泛型
泛型代码在何处被实例化,会影响它所使用的优化设置。因此,用 profile 覆盖来调整某个 crate 的优化级别时,可能产生一些微妙的问题。比如,你提高了某个定义了泛型函数的依赖项的优化级别,但这些泛型函数在你的本地 crate 中使用时可能并没有被优化。这是因为泛型代码可能在实例化它的 crate 中生成,因此会使用该 crate 的优化设置。
例如,nalgebra 是一个定义向量和矩阵的库,大量使用了泛型参数。如果你在本地代码中定义了具体的 nalgebra 类型(如 Vector4<f64>)并调用其方法,对应的 nalgebra 代码会在你的 crate 内部实例化并构建。因此,如果你试图通过 profile override 来提高 nalgebra 的优化级别,这可能并不会带来更快的性能。
此外,rustc 存在某些优化机制,会尝试在不同 crate 之间共享单态化的泛型代码。当 opt-level 为 2 或 3 时,crate 既不会使用其他 crate 的单态化泛型,也不会导出本地定义的单态化项供其他 crate 共享。在调试依赖项的优化配置时,可以尝试使用 opt-level 1,它能在应用部分优化的同时,允许单态化项被共享。