# GCC Performance Tuning **Performance optimization is iterative:** measure, identify bottlenecks, apply targeted optimizations, measure again. Start with compiler flags, then optimize code if needed. **Baseline:** Compile with standard flags and benchmark: ```bash gcc -O3 -march=native -g -o app app.c ./app < large_input # time this ``` Establish a baseline. Everything else is a delta from this. **Profile to find bottlenecks:** ```bash perf record -g ./app < input perf report ``` Use [[perf]] to identify which functions consume the most CPU. This tells you where to focus optimization effort. **Compiler-level optimization:** 1. **Vectorization:** Ensure hot loops are vectorized: ```bash gcc -O3 -march=native -fopt-info-vec-missed -c hot_loop.c | grep missed ``` If loops that should vectorize don't, restructure the code (improve memory access patterns, eliminate data dependencies). 2. **Inlining:** Hot function calls should be inlined: ```bash gcc -O3 -Winline -o app app.c 2>&1 | grep inline ``` If important functions aren't inlined, try `-finline-functions` or mark them explicitly `inline`. 3. **LTO:** For multi-file programs: ```bash gcc -O3 -flto -o app a.c b.c c.c # enables cross-file optimization ``` 4. **PGO:** Profile-guided optimization uses real execution data: ```bash gcc -O3 -fprofile-generate -o app app.c ./app < training_input # profile with representative data gcc -O3 -fprofile-use -o app app.c # recompile optimizing for actual patterns ``` **Code-level optimization:** If compiler optimizations aren't enough: 1. **Memory access patterns:** Sequential access (unit stride) is faster than indirect. Restructure data layouts (structure-of-arrays instead of array-of-structures). 2. **Loop unrolling:** Manually unroll small loops to increase instruction-level parallelism: ```c // Loop unrolled 4x for (int i = 0; i < n; i += 4) { result[i] = a[i] + b[i]; result[i+1] = a[i+1] + b[i+1]; result[i+2] = a[i+2] + b[i+2]; result[i+3] = a[i+3] + b[i+3]; } ``` 3. **Cache optimization:** Improve data locality. Work on cache-sized chunks, reuse loaded data before eviction. 4. **Reduce branches:** Data-dependent branches mispredict and stall pipelines. Use conditional moves or branchless algorithms. **Benchmarking best practices:** ```bash # Run multiple times and average for i in {1..5}; do time ./app < input done # Warm up CPU, reduce OS noise ./app < warmup_input # warm up perf stat -r 10 ./app < input # average 10 runs ``` Run multiple iterations and average—system noise makes single runs unreliable. **Amdahl's law:** Optimize the hottest functions first. A 2x speedup in code that consumes 50% of time is a 1.33x overall speedup. Focus optimization effort on the bottleneck. **Trade-offs:** Faster code often means larger binary (from aggressive inlining, unrolling) or more memory (LTO compilation). Verify the trade-off is worth it—measure both speed and size. **Compiler vs. hardware:** Sometimes the bottleneck is hardware-limited (memory bandwidth, cache size), not compiler quality. In that case, code restructuring (improving cache locality, reducing bandwidth usage) helps more than compiler flags.