# Profile-guided Optimization

`rustc` supports doing profile-guided optimization (PGO).
This chapter describes what PGO is, what it is good for, and how it can be used.

## What Is Profile-Guided Optimization?

The basic concept of PGO is to collect data about the typical execution of
a program (e.g. which branches it is likely to take) and then use this data
to inform optimizations such as inlining, machine-code layout,
register allocation, etc.

There are different ways of collecting data about a program's execution.
One is to run the program inside a profiler (such as `perf`) and another
is to create an instrumented binary, that is, a binary that has data
collection built into it, and run that.
The latter usually provides more accurate data and it is also what is
supported by `rustc`.

## Usage

Generating a PGO-optimized program involves following a workflow with four steps:

1. Compile the program with instrumentation enabled
   (e.g. `rustc -Cprofile-generate=/tmp/pgo-data main.rs`)
2. Run the instrumented program (e.g. `./main`) which generates a
   `default_<id>.profraw` file
3. Convert the `.profraw` file into a `.profdata` file using
   LLVM's `llvm-profdata` tool
4. Compile the program again, this time making use of the profiling data
   (for example `rustc -Cprofile-use=merged.profdata main.rs`)

An instrumented program will create one or more `.profraw` files, one for each
instrumented binary. E.g. an instrumented executable that loads two instrumented
dynamic libraries at runtime will generate three `.profraw` files. Running an
instrumented binary multiple times, on the other hand, will re-use the
respective `.profraw` files, updating them in place.

These `.profraw` files have to be post-processed before they can be fed back
into the compiler. This is done by the `llvm-profdata` tool. This tool
is most easily installed via

```bash
rustup component add llvm-tools-preview
```

Note that installing the `llvm-tools-preview` component won't add
`llvm-profdata` to the `PATH`. Rather, the tool can be found in:

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

Alternatively, an `llvm-profdata` coming with a recent LLVM or Clang
version usually works too.

The `llvm-profdata` tool merges multiple `.profraw` files into a single
`.profdata` file that can then be fed back into the compiler via
`-Cprofile-use`:

```bash
# 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
```

### A Complete Cargo Workflow

Using this feature with Cargo works very similar to using it with `rustc`
directly. Again, we generate an instrumented binary, run it to produce data,
merge the data, and feed it back into the compiler. Some things of note:

- We use the `RUSTFLAGS` environment variable in order to pass the PGO compiler
  flags to the compilation of all crates in the program.

- We pass the `--target` flag to Cargo, which prevents the `RUSTFLAGS`
  arguments to be passed to Cargo build scripts. We don't want the build
  scripts to generate a bunch of `.profraw` files.

- We pass `--release` to Cargo because that's where PGO makes the most sense.
  In theory, PGO can also be done on debug builds but there is little reason
  to do so.

- It is recommended to use *absolute paths* for the argument of
  `-Cprofile-generate` and `-Cprofile-use`. Cargo can invoke `rustc` with
  varying working directories, meaning that `rustc` will not be able to find
  the supplied `.profdata` file. With absolute paths this is not an issue.

- It is good practice to make sure that there is no left-over profiling data
  from previous compilation sessions. Just deleting the directory is a simple
  way of doing so (see `STEP 0` below).

This is what the entire workflow looks like:

```bash
# 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
```

### Troubleshooting

- It is recommended to pass `-Cllvm-args=-pgo-warn-missing-function` during the
  `-Cprofile-use` phase. LLVM by default does not warn if it cannot find
  profiling data for a given function. Enabling this warning will make it
  easier to spot errors in your setup.

- There is a [known issue](https://github.com/rust-lang/cargo/issues/7416) in
  Cargo prior to version 1.39 that will prevent PGO from working correctly. Be
  sure to use Cargo 1.39 or newer when doing PGO.

## Further Reading

`rustc`'s PGO support relies entirely on LLVM's implementation of the feature
and is equivalent to what Clang offers via the `-fprofile-generate` /
`-fprofile-use` flags. The [Profile Guided Optimization][clang-pgo] section
in Clang's documentation is therefore an interesting read for anyone who wants
to use PGO with Rust.

[clang-pgo]: https://clang.llvm.org/docs/UsersManual.html#profile-guided-optimization

## Community Maintained Tools

As an alternative to directly using the compiler for Profile-Guided Optimization,
you may choose to go with `cargo-pgo`, which has an intuitive command-line API
and saves you the trouble of doing all the manual work. You can read more about
it in [cargo-pgo repository][cargo-pgo].

[cargo-pgo]: https://github.com/Kobzol/cargo-pgo

For the sake of completeness, here are the corresponding steps using `cargo-pgo`:

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

cargo pgo build
cargo pgo optimize
```

These steps will do the following just as before:

1. Build an instrumented binary from the source code.
2. Run the instrumented binary to gather PGO profiles.
3. Use the gathered PGO profiles from the last step to build an optimized binary.
