基于配置文件的优化

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 编译器标志传递给程序中所有板条箱的编译。

  • 我们将 --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 配置文件构建一个优化的二进制文件。