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:
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:
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:
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).
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.
gcc -O3 -flto -o app a.c b.c c.c # enables cross-file optimization
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:
// 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]; }
Benchmarking best practices:
# 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.