入门 The Cargo Team 2026-09-13 15:48:21 · 0 阅读

第18章 Cargo 配置:Profile 与编译优化详解

Profiles 提供了一种调整编译器设置的方式,从而优化或影响调试符号等特性。

Cargo 内置了 4 种 profile:devreleasetestbench。如果未在命令行指定 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 标志,该标志决定了编译后二进制文件中包含的调试信息数量。

有效选项如下:

  • 0false"none":不包含任何调试信息,release 模式的默认设置
  • "line-directives-only":仅包含行指令信息。对于 nvptx* 目标架构,这会启用 性能剖析(profiling)。其他用途下,line-tables-only 是兼容性更好的选择
  • "line-tables-only":仅包含行表信息。生成最少的调试信息以支持带文件名和行号的回溯,但不包含变量或函数参数等信息
  • 1"limited":不包含类型或变量级信息的调试信息。比 line-tables-only 生成更详细的模块级信息
  • 2true"full":完整的调试信息,dev 模式的默认设置

有关各选项的具体作用,请查阅 rustc 关于 debuginfo 的文档。

根据你的需求,可能还需要配置 split-debuginfo 选项。

MSRV: nonelimitedfullline-directives-onlyline-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"

也可以用布尔值 truefalse 来配置: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=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 时,该依赖项可能需要构建两次——一次按普通依赖构建,一次按覆盖后的构建设置构建。这可能会增加首次构建的时间。

各设置的生效优先级按以下顺序决定(先匹配者优先):

  1. [profile.dev.package.name] — 指定名称的包。
  2. [profile.dev.package."*"] — 所有非 workspace 成员。
  3. [profile.dev.build-override] — 仅用于构建脚本、proc macro 及其依赖项。
  4. [profile.dev]Cargo.toml 中的设置。
  5. Cargo 内置的默认值。

覆盖配置不能指定 panicltorpath 设置。

覆盖与泛型

泛型代码在何处被实例化,会影响它所使用的优化设置。因此,用 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,它能在应用部分优化的同时,允许单态化项被共享。

评论 (0)