Profile-guided Optimization(剖面引导优化)

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 找不到给定函数的剖面数据,则不会发出警告。启用此警告将更容易发现设置中的错误。

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

进一步阅读

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

社区维护的工具

作为直接使用编译器进行剖面引导优化的替代方法,您可以选择使用 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 剖面数据来构建优化的二进制文件。