Self-hosted service
Kobzol/cargo-pgo avatar
Kobzol/cargo-pgo

cargo-pgo turns a profile guided optimization run into ordinary cargo commands

Cargo subcommand for optimizing Rust binaries/libraries with PGO and BOLT.

793 stars24 forksRustMIT

At a glance

What is it?
cargo-pgo is a Cargo subcommand that makes profile guided optimization and BOLT usable through the verbs you already know, adding an instrumented build, a profile gathering step and an optimized build that reuses your Cargo arguments. It is MIT licensed, at version 0.3.0, and the README is explicit that BOLT and Docker support are still experimental.
Who is it for?
cargo-pgo fits a team shipping a Rust binary whose performance matters and that has a representative workload it can run to collect profiles, because the tool turns an awkward multi step LLVM workflow into cargo pgo instrument, a run, and cargo pgo optimize.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A cargo plugin, and the shape of the problem it solves

cargo-pgo is a Cargo subcommand for optimizing Rust binaries and libraries with PGO and BOLT, and the upstream projects it drives are the Rust compiler's own profile guided optimization documentation and LLVM's BOLT optimiser.

The problem it solves is not that PGO is hard to do, it is that doing it by hand is tedious and easy to get wrong. Instrumenting a build, collecting the profile data that the instrumented binary writes, then rebuilding with that data is a sequence of steps that has to be repeated for every target, every profile type and every build backend, and each step has its own set of flags and output paths.

The tool turns that into the Cargo verbs you already use. It provides subcommands that wrap common Cargo commands, and it automatically adds the release flag to wrapped commands where it is applicable, since the README says it does not really make sense to perform PGO on debug builds.

It is also small. The package metadata describes it as a Cargo plugin in the development-tools category, and the dependency list is a short one of ordinary Rust crates, clap for the command line, cargo_metadata for reading the project, rustc-demangle for readable symbol names, and which and blake3 for locating and hashing. The crate version is 0.3.0, it targets the Rust 2024 edition with a minimum supported Rust version of 1.85.0, and it is MIT licensed. The current release is v0.3.0 from 2026-01-25, after v0.2.10 the day before, and the last push to the repository was on 2026-04-25.

Three steps, and the warning that comes with them

The README explains the feedback directed optimisation workflow before it explains any command, and that ordering is deliberate.

The first step is to build the binary with instrumentation, a special build that adds instrumentation code to the executable. The second is to gather performance profiles by running the instrumented binary on representative workloads, which generates profile files on disk that will then be used to optimise the binary. The third is to build an optimised binary using those generated profiles.

The advice in the middle step is where projects like this usually fail in practice, and the README is specific. Try to gather as much data as possible, and ideally exercise all the important parts of the codebase in the coverage sense.

The warning comes at the end of the third step, and it is the sentence to remember. The binary will be optimised with respect to the profiled workloads, and if you execute it on a substantially different workload, the optimisations might not work, or they might even make your binary slower.

That is not a defect of the tool, it is a property of the technique. A profile is a statement about which paths were hot, and the compiler is entitled to inline and specialise on the strength of that statement. A tool that let you skip the workload step would be a tool that let you ship a binary optimised for a fiction.

The README also tells you to check your environment first, with the info command, at least for PGO, because BOLT is more complicated.

Instrument: one command for each Cargo verb

The instrument subcommand is the entry point, and its shape is the same as the optimize one.

bash
$ cargo pgo instrument [<command>] -- [cargo-args]

The command argument specifies what Cargo command will be executed, it is optional, and by default it is set to build. Additional arguments for Cargo go after the double dash.

There are four ways to produce the profiles, and the README lists them in increasing order of how much control you have.

Building a binary is the simplest and recommended approach. You build an instrumented binary and then run it on some workloads, and the binary ends up in the release directory of your target triple.

Running an instrumented program is the same idea with the execution built in, and cargo pgo run is a shortcut for cargo pgo instrument run. The command instruments the binary and then executes it right away.

Running instrumented tests is the version people reach for first, and the README immediately qualifies it. That command generates profiles by executing tests, but unless your test suite is really comprehensive, it might be better to create a binary and run it on some specific workloads instead. A test suite is usually a small slice of the real behaviour, and a profile built from it is a profile of the slice.

Running instrumented benchmarks is the fourth, and it is the one that most closely matches the intended workload, since benchmarks are by definition representative of something you care about.

Optimize, and the four commands it accepts

Once you have profiles, the optimize subcommand builds the optimised binary, and it takes the same command shape as instrument.

bash
$ cargo pgo optimize [<command>] -- [cargo-args]

The supported commands are build, run, test and bench, and the default is build.

bash
$ cargo pgo optimize build
$ cargo pgo optimize run
$ cargo pgo optimize bench
$ cargo pgo optimize test

The profiles themselves are ordinary LLVM profile data, and you can look at them with the standard tool, which is worth doing before you trust them.

console
$ llvm-profdata show <profile>.profdata

The fact that the profiles are in a documented, standard format is the reason you can inspect them. A profile that is only meaningful to the tool that produced it is a profile you cannot check, and a profile you cannot check is a profile you cannot argue about when the optimised binary turns out to be slower on a workload you did not profile.

For a project that wants to make this part of continuous integration, the README points at a working example, the pgo workflow in the ci directory of the repository, which shows how to run cargo-pgo on GitHub Actions.

BOLT produces separate binaries and is the experimental path

BOLT is the second optimiser, and the README is clear that it works differently.

Using BOLT with cargo-pgo is similar to using PGO, but you either have to build BOLT manually or download it from the GitHub releases archive for LLVM 16 and later, and support for BOLT is currently in an experimental stage.

The reason it works differently is stated plainly. BOLT is not supported directly by rustc, so the instrumentation and optimisation commands are not directly applied to binaries built by rustc. Instead, cargo-pgo creates additional binaries that you have to use for gathering profiles and executing the optimised code.

So the BOLT flow produces a binary with a suffix. The instrumented binary is called with -bolt-instrumented added to the name, and you run it on several workloads to gather as much data as possible. The optimised binary is then named with -bolt-optimized.

One detail is easy to miss. For BOLT, the profile gathering step is optional, and you can simply run the optimisation step without any profiles, although it will probably not have a large effect. That is a difference from PGO, where the profiles are the point.

The two can also be combined, which the README answers with a yes. You generate PGO profiles first and then use BOLT on the already PGO optimised binary. If you are going to take the experimental path, that is the order.

The container that carries LLVM 18 and a musl target

For BOLT the README says it is highly recommended to use Docker, and the repository ships a Dockerfile that is worth reading because it solves several installation problems at once.

The build stage uses a rust 1.85 slim image and installs the tool with cargo install from the local path. The runtime stage starts from the same base image, copies the binary into place, and then adds the LLVM apt repository for bookworm and installs bolt-18 along with musl-tools.

The next four lines are the interesting part. The image creates a set of symlinks so the generic names resolve to the versioned binaries, linking merge-fdata-18 to merge-fdata, llvm-bolt-18 to llvm-bolt, and the BOLT runtime archive to a plain /usr/lib path. The tool looks for the unsuffixed names, and the image provides them without a version pin in the workflow.

It also adds the llvm-tools-preview rustup component, which is the PGO half of the requirement, and the x86_64-unknown-linux-musl target, so a fully static build is possible. The work directory is set to /workdir, which matches the volume mount in the documented run command.

Building and running the container is two commands.

bash
git clone https://github.com/Kobzol/cargo-pgo.git && cd cargo-pgo
docker build -t cargo-pgo .
bash
docker run -v $(pwd):/workdir --rm -it cargo-pgo

Inside the container you run cargo-pgo as you would on your system, and the --rm argument means the container is removed after you exit.

Where cargo-pgo is the wrong tool

Three situations rule it out.

The first is a project with no representative workload. The whole method depends on running the binary on something that resembles production use, and the README says as much when it advises against using the test suite unless it is really comprehensive. A library with no binary, or a service whose hot paths cannot be exercised offline, cannot generate a useful profile, and the optimise step then has nothing to work with.

The second is a build environment that cannot carry a C toolchain or a container. Because the upstream allocator and the BOLT runtime are C components, the optimised binary links against them, and the build needs an LLVM installation with the right binaries on the path. A reproducible build that must run on one image with no LLVM packages is a poor fit.

The third is a project where the risk of a slower binary outweighs the gain. A profile driven build is a bet that the profiled workload is representative, and the README is explicit that a different workload can make the binary slower. For a service with unpredictable inputs, the gain is uncertain, and a regression would be found in production rather than in a benchmark.

For a batch job, a CLI or a service with a small set of known workloads, the same bet is a good one, and the tool makes it cheap to place in continuous integration.

Editorial conclusion

cargo-pgo fits a team shipping a Rust binary whose performance matters and that has a representative workload it can run to collect profiles, because the tool turns an awkward multi step LLVM workflow into cargo pgo instrument, a run, and cargo pgo optimize. It is the wrong choice if you have no representative workload, since the README warns that a binary optimised against the wrong profile can be slower, and it is the wrong choice if you cannot ship a C toolchain or a container in your build. Before you adopt it, run cargo pgo info to check that your environment can produce profiles at all, and decide whether you need BOLT on top of PGO, since that path is the experimental one.

Frequently asked questions

How do I install cargo-pgo?

Run cargo install cargo-pgo. You also need the llvm-profdata binary for PGO, and llvm-bolt and merge-fdata for BOLT, with the PGO helper available by adding the llvm-tools-preview component to your toolchain with rustup component add llvm-tools-preview.

What are the three steps of a profile guided optimization run?

Build the binary with instrumentation, run the instrumented binary on representative workloads to gather profile files on disk, and then build an optimised binary using those profiles. The README warns that a binary optimised for one workload can be slower on a substantially different one.

How do I generate PGO profiles with cargo-pgo?

Use cargo pgo instrument followed by the Cargo command you want, such as cargo pgo build, cargo pgo run, cargo pgo test or cargo pgo bench, with any Cargo arguments after a double dash. The README calls building a binary and running it on workloads the simplest and recommended approach.

How is BOLT different from PGO in cargo-pgo?

BOLT is not supported directly by rustc, so cargo-pgo creates separate binaries, one with -bolt-instrumented added and one with -bolt-optimized, which you use for gathering profiles and running the optimised code. BOLT also requires you to build it yourself or download it for LLVM 16 and later, and the README says BOLT support is currently experimental.

What does the cargo-pgo Docker image provide?

It installs the tool, adds the LLVM 18 apt repository with bolt-18 and musl-tools, symlinks the versioned binaries to the generic names merge-fdata, llvm-bolt and the BOLT runtime archive, adds the llvm-tools-preview component and the x86_64-unknown-linux-musl target, and works in /workdir so it matches the documented volume mount.

Official sources

  1. Issues
  2. Kobzol/cargo-pgo on GitHub
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/kobzol-cargo-pgo.svg)](https://hysenlabs.com/projects/kobzol-cargo-pgo)

Was this useful?