配置文件引导优化

rustc 支持进行配置文件引导优化 (PGO)。本章介绍 PGO 是什么、它的优点以及如何使用它。

什么是配置文件引导优化?

PGO 的基本概念是收集程序典型执行过程的数据(例如,它可能采取哪些分支),然后利用这些数据来指导优化,例如内联、机器码布局、寄存器分配等。

收集程序执行数据有不同的方法。一种是在分析器(例如 perf)中运行程序,另一种是创建一个插桩二进制文件,即内置数据收集功能的二进制文件,然后运行它。后者通常提供更准确的数据,这也是 rustc 支持的方式。

用法

生成 PGO 优化程序包括以下四个步骤的工作流程

  1. 启用插桩编译程序(例如 rustc -Cprofile-generate=/tmp/pgo-data main.rs
  2. 运行插桩程序(例如 ./main),这将生成一个 default_<id>.profraw 文件
  3. 使用 LLVM 的 llvm-profdata 工具将 .profraw 文件转换为 .profdata 文件
  4. 再次编译程序,这次使用剖析数据(例如 rustc -Cprofile-use=merged.profdata main.rs

插桩程序会创建一个或多个 .profraw 文件,每个插桩二进制文件对应一个。例如,一个在运行时加载两个插桩动态库的插桩可执行文件会生成三个 .profraw 文件。另一方面,多次运行同一个插桩二进制文件会重用相应的 .profraw 文件,并在原地更新它们。

这些 .profraw 文件在反馈给编译器之前需要进行后处理。这通过 llvm-profdata 工具完成。安装此工具最简单的方法是

rustup component add llvm-tools-preview

请注意,安装 llvm-tools-preview 组件不会将 llvm-profdata 添加到 PATH 中。相反,该工具可以在以下位置找到

~/.rustup/toolchains/<toolchain>/lib/rustlib/<target-triple>/bin/

或者,随附于最新 LLVM 或 Clang 版本的 llvm-profdata 通常也可用。

llvm-profdata 工具将多个 .profraw 文件合并成一个 .profdata 文件,然后可以通过 -Cprofile-use 将其反馈给编译器

# STEP 1: Compile the binary with instrumentation
rustc -Cprofile-generate=/tmp/pgo-data -O ./main.rs

# STEP 2: Run the binary a few times, maybe with common sets of args.
#         Each run will create or update `.profraw` files in /tmp/pgo-data
./main mydata1.csv
./main mydata2.csv
./main mydata3.csv

# STEP 3: Merge and post-process all the `.profraw` files in /tmp/pgo-data
llvm-profdata merge -o ./merged.profdata /tmp/pgo-data

# STEP 4: Use the merged `.profdata` file during optimization. All `rustc`
#         flags have to be the same.
rustc -Cprofile-use=./merged.profdata -O ./main.rs

完整的 Cargo 工作流程

在 Cargo 中使用此功能与直接使用 rustc 非常相似。同样,我们生成一个插桩二进制文件,运行它以生成数据,合并数据,然后将其反馈给编译器。需要注意的一些事项:

  • 我们使用 RUSTFLAGS 环境变量将 PGO 编译器标志传递给程序中所有 crate 的编译。

  • 我们将 --target 标志传递给 Cargo,这会阻止将 RUSTFLAGS 参数传递给 Cargo 构建脚本。我们不希望构建脚本生成一堆 .profraw 文件。

  • 我们将 --release 传递给 Cargo,因为 PGO 在发布构建中最有意义。理论上,PGO 也可以在调试构建上进行,但这样做没有什么理由。

  • 建议对 -Cprofile-generate-Cprofile-use 的参数使用 绝对路径。Cargo 可以使用不同的工作目录调用 rustc,这意味着 rustc 可能无法找到提供的 .profdata 文件。使用绝对路径则不会出现此问题。

  • 确保没有先前编译会话留下的剖析数据是一个好习惯。直接删除目录是一种简单的方法(参见下面的 STEP 0)。

完整的工作流程如下所示

# STEP 0: Make sure there is no left-over profiling data from previous runs
rm -rf /tmp/pgo-data

# STEP 1: Build the instrumented binaries
RUSTFLAGS="-Cprofile-generate=/tmp/pgo-data" \
    cargo build --release --target=x86_64-unknown-linux-gnu

# STEP 2: Run the instrumented binaries with some typical data
./target/x86_64-unknown-linux-gnu/release/myprogram mydata1.csv
./target/x86_64-unknown-linux-gnu/release/myprogram mydata2.csv
./target/x86_64-unknown-linux-gnu/release/myprogram mydata3.csv

# STEP 3: Merge the `.profraw` files into a `.profdata` file
llvm-profdata merge -o /tmp/pgo-data/merged.profdata /tmp/pgo-data

# STEP 4: Use the `.profdata` file for guiding optimizations
RUSTFLAGS="-Cprofile-use=/tmp/pgo-data/merged.profdata" \
    cargo build --release --target=x86_64-unknown-linux-gnu

故障排除

  • 建议在 -Cprofile-use 阶段传递 -Cllvm-args=-pgo-warn-missing-function。LLVM 默认情况下不会在找不到给定函数的剖析数据时发出警告。启用此警告将更容易发现配置中的错误。

  • Cargo 1.39 版本之前存在一个已知问题,会导致 PGO 无法正常工作。进行 PGO 时请务必使用 Cargo 1.39 或更高版本。

延伸阅读

rustc 的 PGO 支持完全依赖于 LLVM 的实现,并且与 Clang 通过 -fprofile-generate / -fprofile-use 标志提供的功能等效。因此,Clang 文档中的配置文件引导优化部分对于任何想在 Rust 中使用 PGO 的人来说都是值得一读的。

社区维护的工具

作为直接使用编译器进行配置文件引导优化的替代方案,您可以选择使用 cargo-pgo,它具有直观的命令行 API,省去了您所有手动操作的麻烦。您可以通过此链接访问其仓库阅读更多信息:https://github.com/Kobzol/cargo-pgo

为了完整性,以下是使用 cargo-pgo 的相应步骤

# Install if you haven't already
cargo install cargo-pgo

cargo pgo build
cargo pgo optimize

这些步骤将与之前一样执行以下操作

  1. 从源代码构建一个插桩二进制文件。
  2. 运行插桩二进制文件以收集 PGO 配置文件。
  3. 使用上一步收集的 PGO 配置文件构建优化后的二进制文件。