LTO, PGO, Vectorization
This chapter covers three advanced optimization techniques: link-time optimization (LTO), profile-guided optimization (PGO), and auto-vectorization.
Why This Matters
Whole-program and profile-driven optimization can yield significant speedups beyond -O3. Vectorization uses SIMD to process multiple data elements at once. These are the tools that separate "correct" from "fast."
Prerequisites
c.opt.1— optimizer basics.
Core Concept
Link-time optimization (LTO)
Normally the compiler optimizes each translation unit separately; the linker just combines objects. LTO defers optimization until link time, allowing inlining and other passes across translation units.
gcc -flto -O2 -c a.c
gcc -flto -O2 -c b.c
gcc -flto -O2 a.o b.o -o app
Profile-guided optimization (PGO)
PGO runs the program on representative inputs to collect a profile, then recompiles using that profile to guide inlining, branch layout, and hot/cold code splitting.
gcc -O2 -fprofile-generate app.c -o app
./app < training-input # produces .gcda files
gcc -O2 -fprofile-use app.c -o app-optimized
Auto-vectorization
The compiler transforms loops to use SIMD instructions (SSE/AVX/NEON) when it can prove the iterations are independent and the accesses are aligned or handled.
for (int i = 0; i < n; i++)
c[i] = a[i] + b[i]; /* vectorizable */
Enable with -O3 (or -O2 with -ftree-vectorize on GCC), and inspect with -fopt-info-vec.
How It Works
LTO serializes the compiler's IR into the object file, then re-optimizes at link time. PGO instruments the binary to record branch/call frequencies, which a later compilation reads. Vectorization analyzes loop-carried dependencies and emits packed SIMD operations.
Variations
ThinLTO
Clang's ThinLTO scales LTO to large programs by partitioning the IR, trading a little optimization for much faster link times.
Explicit SIMD
When auto-vectorization fails, you can write intrinsics directly (c.perf.4).
Common Mistakes
- Enabling LTO but not using the same flags at compile and link time.
- Using PGO profiles from unrealistic inputs.
- Assuming every loop vectorizes (aliasing and dependencies often block it).
Undefined Behavior
- Vectorization (like all optimization) assumes no UB; UB can cause
miscompiled vectorized code.
Portability
- LTO/PGO flags differ slightly between GCC and Clang. Vectorization is
hardware-dependent (SSE/AVX/NEON).
Under the Hood
LTO stores GIMPLE/LLVM IR in object sections. PGO emits counters that update on each edge/call. The vectorizer uses SLP/loop vectorization on SSA form to find independent lanes.
Practical Usage
- Use LTO for release builds of multi-file programs.
- Use PGO when you have realistic training workloads.
- Write loops that are vectorization-friendly (contiguous, no early exits, no
aliasing via restrict).
Exercises
1. Build a multi-file program with and without LTO and compare size/speed. 2. Run a PGO cycle and observe the profile files. 3. Write a vectorizable loop and confirm with -fopt-info-vec.
Deep Challenge
Take a loop that does not vectorize (due to possible aliasing) and make it vectorizable by adding restrict and restructuring. Explain each blocker you removed.
Related Concepts
c.opt.1— optimizer basics.c.perf.4— SIMD.c.ptr.restrict— enabling vectorization.
References
- GCC/Clang LTO and PGO documentation, vectorizer docs.
Verification
- LTO/PGO/vectorization flags and behaviors are documented.
VERIFIED - No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] LTO
- [ ] PGO
- [ ] Auto-vectorization
- [ ] ThinLTO
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.opt.lto | 0 | 6 |
| c.opt.pgo | 0 | 5 |
| c.opt.vectorize | 0 | 6 |