1
0
Fork
You've already forked cmake-pgo
0
CMake Profile-Guided Optimization abstraction for GCC and Clang
  • CMake 87%
  • C 13%
typedlambda ceca6c7bac Clang PGO: link instrumented twins like their originals
The instrumented _pgo_gen twins inherited include dirs, defines, compile
options, and language standard from their originals, but not the link
interface.
2026年06月05日 09:54:49 +02:00
app CMake PGO abstraction for GCC and Clang 2026年06月01日 13:39:29 +02:00
bench CMake PGO abstraction for GCC and Clang 2026年06月01日 13:39:29 +02:00
cmake Clang PGO: link instrumented twins like their originals 2026年06月05日 09:54:49 +02:00
external Add pgo convenience target and ExternalProject entry point 2026年06月05日 09:52:42 +02:00
mathlib install rules for mathlib so the repo is consumable via pgo_optimize_external 2026年06月05日 09:53:51 +02:00
test CMake PGO abstraction for GCC and Clang 2026年06月01日 13:39:29 +02:00
.gitattributes CMake PGO abstraction for GCC and Clang 2026年06月01日 13:39:29 +02:00
.gitignore CMake PGO abstraction for GCC and Clang 2026年06月01日 13:39:29 +02:00
CMakeLists.txt CMake PGO abstraction for GCC and Clang 2026年06月01日 13:39:29 +02:00
LICENSE add license file 2026年06月05日 09:52:26 +02:00
README.md Driver uses CMake's default generator instead of forcing Ninja 2026年06月05日 09:54:19 +02:00

CMake PGO Abstraction

cmake/pgo.cmake + cmake/pgo_driver.cmake (+ cmake/pgo_external.cmake) add Profile-Guided Optimization to an existing CMake target, for GCC and Clang.

Want a plain cmake --build to just produce an optimized binary, with no driver, stages, or second build -- on both compilers? Consume the library via the external dependency entry point; it hides the GCC two-pass inside the sub-build.

Usage

include(pgo) # cmake/ on CMAKE_MODULE_PATH
add_library(mathlib STATIC ...) # your existing target
add_executable(demo_bench ...) # drivers that exercise it
add_executable(demo_app ...)pgo_train_runs(demo_bench
 ARGS "--size 100 --iters 200"
 ARGS "--size 10000 --iters 50")pgo_train_runs(demo_app
 ARGS "data/small.txt")pgo_optimize(mathlib TRAIN_TARGETS demo_bench demo_app)

Building the optimized library

Three ways, all keeping the same pgo_train_runs / pgo_optimize setup above:

  1. Driver (one command, fully automated, both compilers):

    CC=clang cmake -DSRC=. -DBUILD=build -P cmake/pgo_driver.cmake # or CC=gcc
    

    After it finishes, mathlib is built with -fprofile-use.

  2. Convenience targets -- pgo_optimize creates a per-target <target>_pgo target and extends a single aggregate pgo target (which drives every optimized target at once):

    • Clang: cmake --build build --target pgo (or mathlib_pgo) builds the optimized library in one pass. A plain cmake --build build also produces it -- on Clang the optimization is in-tree, so PGO is effectively the default build.
    • GCC: configure with -DPGO_STAGE=generate, then cmake --build build --target pgo builds the instrumented library, runs training, and prints the exact reconfigure + build commands to produce the optimized library (-DPGO_STAGE=use). A plain cmake --build cannot be the PGO default on GCC -- the two-pass needs the reconfigure between builds. For a seamless default build on GCC, use the external dependency (option 3).
  3. As an external dependency -- a seamless single build for both compilers; see "External dependency" below.

Requiring PGO

If a library is only correct when profile-guided, add REQUIRED so a non-PGO build fails loudly instead of silently producing an unoptimized binary:

pgo_optimize(mylib TRAIN_TARGETS trainer REQUIRED)
  • GCC, default (off) stage: cmake -S . -B build aborts at configure time and prints the exact PGO build commands (the staged commands and the one-shot driver). The generate stage builds but warns that it is the instrumented, not-final library; the use stage is accepted.
  • Clang: a plain cmake --build already applies PGO in-tree, so REQUIRED is a no-op -- there are no separate steps to enforce.
  • Consumers using pgo_optimize_external() satisfy the requirement automatically (the driver configures generate then use).
  • REQUIRED and GENERATE_ONLY are mutually exclusive.

API

  • pgo_train_runs(<exe> ARGS "<args>" [ARGS ...]) - one run per ARGS; repeated calls accumulate; all runs feed one profile.
  • pgo_optimize(<target> TRAIN_TARGETS <exe>... [PROFILE_DIR <dir>] [GENERATE_ONLY] [REQUIRED]) - also defines a per-target <target>_pgo and an aggregate pgo build target (see Building, option 2). REQUIRED fails a non-PGO configuration (see "Requiring PGO").
  • pgo_optimize_external(<name> SOURCE_DIR <proj> LIBRARY <lib> [INCLUDE_DIRS ...] [TOOLCHAIN <file>] [GENERATOR <gen>] [CMAKE_ARGS ...]) - build a PGO-enabled sub-project externally and consume its library as an IMPORTED target.

How it works

  • Clang builds an instrumented twin of the target + drivers, runs the training, llvm-profdata merges the result, and applies -fprofile-use to the target in the same build (profiles match by function hash).
  • GCC keys .gcda by object path, so a twin cannot match. The driver does a same-build-directory two-pass: generate -> build -> train -> use -> rebuild, reusing identical object paths so the profile matches.

External dependency

cmake/pgo_external.cmake builds a PGO-enabled sub-project as an ExternalProject and exposes its library as an IMPORTED target. The whole PGO process (the GCC two-pass included) runs inside the sub-build during a normal cmake --build, so the consumer never deals with stages or the driver:

include(pgo_external)pgo_optimize_external(mathlib_opt
 SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib # a self-contained, PGO-enabled project
 LIBRARY mathlib # static lib target it builds
 INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/lib/include)add_executable(consumer consumer.c)target_link_libraries(consumer PRIVATE mathlib_opt)
CC=clang cmake -G Ninja -S . -B build # or -DCMAKE_C_COMPILER=gcc
cmake --build build # sub-build runs PGO; consumer links the result

The SOURCE_DIR is an ordinary CMake project that must:

  • call pgo_optimize(<LIBRARY> TRAIN_TARGETS ...) (the sub-build requires it) and register training runs with pgo_train_runs;
  • install the static library and its headers: install(TARGETS <LIBRARY> ARCHIVE DESTINATION lib) -- DESTINATION must be exactly lib, since the import path is <install>/lib/lib<LIBRARY>.a;
  • build optimized (default Release / -O2).

The consumer's compiler, build type, and TOOLCHAIN / CMAKE_ARGS are forwarded into the sub-build so the ABI matches (use TOOLCHAIN for cross builds, e.g. aarch64). Note that training runs the instrumented binary on the build host, so the sub-build must be native -- your native aarch64 is fine; an x86 -> aarch64 cross would need an emulator.

Two ready examples: the self-contained external/ demo, and this repo's own top-level project (its mathlib carries install rules) -- point SOURCE_DIR at the repo root with LIBRARY mathlib.

Requirements

  • CMake >= 3.22 and a single-config generator. The driver uses CMake's default generator (e.g. Unix Makefiles, or Ninja) unless you pass -DGEN=<generator>; any single-config generator works (multi-config generators are out of scope).
  • Clang path needs llvm-profdata on PATH.
  • Native build+run (instrumented binary runs on the build host). Cross/on-device collection is out of scope.
  • Build at a real optimization level (the demo defaults to Release/-O2); PGO is a no-op at -O0.