Profile-guided optimization
Contract
| Field |
Bound contract |
| Trigger |
The user asks how to apply PGO, how -fprofile-generate and -fprofile-use fit together, how to collect a representative profile, or how BOLT differs from PGO. |
| Authority |
Read-only. The skill emits the build sequence, collection commands, and a measurement plan to chat; the user runs them. Rollback is not needed. No remote mutation. |
| Side effect |
Chat output. |
| Done |
A two-stage build sequence for the user's compiler is reported with the profile format named, the workload checklist is applied, and the speedup claim rests on a before-and-after measurement command rather than a number. |
Inputs
- Compiler and version: required. GCC 16.2 or Clang 23.1.0 are current stable; each has its own profile format.
- A representative workload the instrumented binary can run: required.
- Build system: required for wiring; CMake shown below.
- Whether
perf with branch sampling is available: required for SamplePGO and BOLT.
- Baseline flags already in use: required; PGO layers on
-O2 or -O3.
Procedure
- Decide whether PGO applies. Standard optimization comes first:
-O2 or -O3, and -march= for the deployment CPU. PGO pays off on code where static heuristics guess wrong: branch-heavy paths, many hot and cold functions, call-heavy code where inlining decisions matter. Do not promise a percentage; the gain is measured in step 7. Done when: the user knows PGO is a candidate and what will decide it.
- Run the GCC flow. Instrument:
gcc -O2 -fprofile-generate -fprofile-dir=./pgo-data prog.c -o prog. Run the workload; .gcda files land in ./pgo-data/. Rebuild: gcc -O2 -fprofile-use -fprofile-dir=./pgo-data -fprofile-correction prog.c -o prog. Keep the same output name in both builds, because GCC derives the .gcda file name from the object name and a renamed second build reports -Wmissing-profile. -fprofile-correction reconciles counts from multi-threaded runs; include it. Done when: the three commands share one output name.
- Run the Clang instrumented flow. Instrument:
clang -O2 -fprofile-instr-generate prog.c -o prog_instr. Run the workload; it writes default.profraw, or per-process files with LLVM_PROFILE_FILE="prog-%p.profraw". Merge: llvm-profdata merge -output=prog.profdata *.profraw. Rebuild: clang -O2 -fprofile-instr-use=prog.profdata prog.c -o prog_pgo. Weight workloads with -weighted-input=3,a.profraw -weighted-input=1,b.profraw; inspect with llvm-profdata show --all-functions prog.profdata; compare two profiles with llvm-profdata overlap a.profdata b.profdata. Done when: the merged .profdata exists and the rebuild command names it.
- Add context-sensitive PGO when inlining decisions matter: take the stage-1
.profdata, build clang -O2 -fprofile-use=stage1.profdata -fcs-profile-generate, run the workload, merge the new .profraw files into cs.profdata, and build the final binary with -fprofile-use=cs.profdata. Done when: the second-stage profile feeds the final build.
- Use sampling when instrumentation overhead is unacceptable. Build with
-fno-omit-frame-pointer, record perf record -b -e cycles:u ./prog < workload, convert perf script -F ip,brstack > perf.script, then llvm-profgen --binary=./prog --perfscript=perf.script --output=prog.profdata, and rebuild with -fprofile-sample-use=prog.profdata. GCC's sampling counterpart is -fauto-profile=<file> with a profile converted from perf data. Done when: the sample profile feeds the rebuild.
- Apply BOLT after PGO when instruction-cache layout is the remaining cost. Link with
-Wl,--emit-relocs. Collect perf record -e cycles:u -j any,u -o perf.data -- ./prog < workload, convert perf2bolt -p perf.data -o perf.fdata ./prog, or instrument with llvm-bolt prog -instrument -o prog.instr and run it (profile lands in /tmp/prof.fdata). Optimize: llvm-bolt prog -o prog.bolt -data=perf.fdata -reorder-blocks=ext-tsp -reorder-functions=cdsort -split-functions -split-all-cold -split-eh -dyno-stats. GCC 8 and later need -fno-reorder-blocks-and-partition at compile time for BOLT. Done when: the BOLT binary exists.
- Measure.
hyperfine --runs 5 "./prog_baseline < workload" "./prog_pgo < workload" --export-markdown results.md, or perf stat on each binary. Report the measured ratio and the noise across runs; a PGO or BOLT build that does not beat the baseline on the deployment workload is discarded. Done when: the numbers come from the user's workload.
- Wire CMake with two options,
PGO_INSTRUMENT adding -fprofile-instr-generate to compile and link options and PGO_USE adding -fprofile-instr-use=<path> to both, driven by two configure-build cycles into separate build directories. Done when: the two build directories and the profile path are named.
Failure and recovery
| Failure class |
Behavior |
| Workload not representative |
Apply the checklist: covers the hot paths, runs long enough to exercise them, weights production scenarios higher, excludes debug builds and one-off startup unless startup matters. A tiny synthetic benchmark trains the wrong layout. |
-Wmissing-profile from GCC |
The second build's object name differs from the first; use one output name. |
perf record -b unsupported |
The CPU or VM lacks branch sampling; use instrumented PGO instead of SamplePGO and BOLT. |
llvm-bolt not installed |
BOLT builds as an LLVM project (-DLLVM_ENABLE_PROJECTS=bolt); report it missing rather than substituting. |
| Profile format mismatch |
-fprofile-instr-* and -fprofile-generate/-fprofile-use are different formats in Clang; do not mix them across stages. |
Output
A chat report with the ordered build, run, merge, and rebuild commands for the user's compiler, the optional CS-PGO, SamplePGO, and BOLT steps when they apply, the workload checklist verdict, and the measurement command whose result decides whether the build ships.
1---2name: pgo3description: Use when a C or C++ binary needs profile-guided optimization with GCC or Clang: instrumented or sampled profiles, merging, the two-stage build, or BOLT. Not for base flags: use gcc or clang.4---5
6# Profile-guided optimization
7
8## Contract
9
10| Field | Bound contract |
11|---|---|
12| Trigger | The user asks how to apply PGO, how `-fprofile-generate` and `-fprofile-use` fit together, how to collect a representative profile, or how BOLT differs from PGO. |
13| Authority | Read-only. The skill emits the build sequence, collection commands, and a measurement plan to chat; the user runs them. Rollback is not needed. No remote mutation. |
14| Side effect | Chat output. |
15| Done | A two-stage build sequence for the user's compiler is reported with the profile format named, the workload checklist is applied, and the speedup claim rests on a before-and-after measurement command rather than a number. |
16
17## Inputs
18
19- Compiler and version: required. GCC 16.2 or Clang 23.1.0 are current stable; each has its own profile format.
20- A representative workload the instrumented binary can run: required.
21- Build system: required for wiring; CMake shown below.
22- Whether `perf` with branch sampling is available: required for SamplePGO and BOLT.
23- Baseline flags already in use: required; PGO layers on `-O2` or `-O3`.
24
25## Procedure
26
271. Decide whether PGO applies. Standard optimization comes first: `-O2` or `-O3`, and `-march=` for the deployment CPU. PGO pays off on code where static heuristics guess wrong: branch-heavy paths, many hot and cold functions, call-heavy code where inlining decisions matter. Do not promise a percentage; the gain is measured in step 7. Done when: the user knows PGO is a candidate and what will decide it.
282. Run the GCC flow. Instrument: `gcc -O2 -fprofile-generate -fprofile-dir=./pgo-data prog.c -o prog`. Run the workload; `.gcda` files land in `./pgo-data/`. Rebuild: `gcc -O2 -fprofile-use -fprofile-dir=./pgo-data -fprofile-correction prog.c -o prog`. Keep the same output name in both builds, because GCC derives the `.gcda` file name from the object name and a renamed second build reports `-Wmissing-profile`. `-fprofile-correction` reconciles counts from multi-threaded runs; include it. Done when: the three commands share one output name.
293. Run the Clang instrumented flow. Instrument: `clang -O2 -fprofile-instr-generate prog.c -o prog_instr`. Run the workload; it writes `default.profraw`, or per-process files with `LLVM_PROFILE_FILE="prog-%p.profraw"`. Merge: `llvm-profdata merge -output=prog.profdata *.profraw`. Rebuild: `clang -O2 -fprofile-instr-use=prog.profdata prog.c -o prog_pgo`. Weight workloads with `-weighted-input=3,a.profraw -weighted-input=1,b.profraw`; inspect with `llvm-profdata show --all-functions prog.profdata`; compare two profiles with `llvm-profdata overlap a.profdata b.profdata`. Done when: the merged `.profdata` exists and the rebuild command names it.
304. Add context-sensitive PGO when inlining decisions matter: take the stage-1 `.profdata`, build `clang -O2 -fprofile-use=stage1.profdata -fcs-profile-generate`, run the workload, merge the new `.profraw` files into `cs.profdata`, and build the final binary with `-fprofile-use=cs.profdata`. Done when: the second-stage profile feeds the final build.
315. Use sampling when instrumentation overhead is unacceptable. Build with `-fno-omit-frame-pointer`, record `perf record -b -e cycles:u ./prog < workload`, convert `perf script -F ip,brstack > perf.script`, then `llvm-profgen --binary=./prog --perfscript=perf.script --output=prog.profdata`, and rebuild with `-fprofile-sample-use=prog.profdata`. GCC's sampling counterpart is `-fauto-profile=<file>` with a profile converted from `perf` data. Done when: the sample profile feeds the rebuild.
326. Apply BOLT after PGO when instruction-cache layout is the remaining cost. Link with `-Wl,--emit-relocs`. Collect `perf record -e cycles:u -j any,u -o perf.data -- ./prog < workload`, convert `perf2bolt -p perf.data -o perf.fdata ./prog`, or instrument with `llvm-bolt prog -instrument -o prog.instr` and run it (profile lands in `/tmp/prof.fdata`). Optimize: `llvm-bolt prog -o prog.bolt -data=perf.fdata -reorder-blocks=ext-tsp -reorder-functions=cdsort -split-functions -split-all-cold -split-eh -dyno-stats`. GCC 8 and later need `-fno-reorder-blocks-and-partition` at compile time for BOLT. Done when: the BOLT binary exists.
337. Measure. `hyperfine --runs 5 "./prog_baseline < workload" "./prog_pgo < workload" --export-markdown results.md`, or `perf stat` on each binary. Report the measured ratio and the noise across runs; a PGO or BOLT build that does not beat the baseline on the deployment workload is discarded. Done when: the numbers come from the user's workload.
348. Wire CMake with two options, `PGO_INSTRUMENT` adding `-fprofile-instr-generate` to compile and link options and `PGO_USE` adding `-fprofile-instr-use=<path>` to both, driven by two configure-build cycles into separate build directories. Done when: the two build directories and the profile path are named.
35
36## Failure and recovery
37
38| Failure class | Behavior |
39|---|---|
40| Workload not representative | Apply the checklist: covers the hot paths, runs long enough to exercise them, weights production scenarios higher, excludes debug builds and one-off startup unless startup matters. A tiny synthetic benchmark trains the wrong layout. |
41| `-Wmissing-profile` from GCC | The second build's object name differs from the first; use one output name. |
42| `perf record -b` unsupported | The CPU or VM lacks branch sampling; use instrumented PGO instead of SamplePGO and BOLT. |
43| `llvm-bolt` not installed | BOLT builds as an LLVM project (`-DLLVM_ENABLE_PROJECTS=bolt`); report it missing rather than substituting. |
44| Profile format mismatch | `-fprofile-instr-*` and `-fprofile-generate`/`-fprofile-use` are different formats in Clang; do not mix them across stages. |
45
46## Output
47
48A chat report with the ordered build, run, merge, and rebuild commands for the user's compiler, the optional CS-PGO, SamplePGO, and BOLT steps when they apply, the workload checklist verdict, and the measurement command whose result decides whether the build ships.