Optimization levels (-O0 through -O3, -Os, -Ofast) enable bundles of passes that transform code. Each level adds more aggressive optimizations, trading compilation time and code size for runtime speed.
-O0 (default) disables optimization entirely. Code compiles instantly but runs slowly and produces predictable output for debugging. Essential for GDB debugging—with optimization, line numbers may not map correctly to source, and variables may be optimized away.
gcc -O0 -g -o app app.c # debug build: no optimization, symbols kept
-O1 enables basic optimizations: dead code elimination, simple inlining, register allocation. Minimal overhead; rarely used explicitly.
-O2 enables most optimizations that don't significantly increase code size: loop unrolling, function inlining, instruction scheduling, strength reduction. Good balance between speed and compile time. Recommended for production builds if not using -O3.
gcc -O2 -o app app.c # standard production build
-O3 adds aggressive optimizations: aggressive inlining, loop vectorization, loop unrolling at larger scales, speculative optimizations. Code is often larger and compilation is slower, but runtime can be 10-30% faster than -O2 on compute-heavy code. Common in HPC.
gcc -O3 -march=native -o app app.c # high-performance build
-Os optimizes for code size instead of speed. Useful for embedded systems where memory is constrained. Typically slower than -O2.
-Ofast enables -O3 plus optimizations that violate strict language semantics—most importantly, it relaxes IEEE floating-point strictness (allows fused multiply-add, reordering of operations). Can break numerically sensitive code that depends on exact rounding behavior. Use only if you've verified correctness or don't care about tiny floating-point differences.
gcc -Ofast -o app app.c # aggressive, may affect numerical precision
Profile-guided optimization (PGO): Compile with -fprofile-generate, run on representative input, then recompile with -fprofile-use to optimize based on actual execution patterns:
gcc -O3 -fprofile-generate -o app_profile app.c ./app_profile < test_input gcc -O3 -fprofile-use -o app app.c
The second compile uses profiling data to make smarter optimization decisions (inlining hot functions, branch prediction, etc.).
Benchmarking: Always compare -O2 and -O3 on your actual workload. Sometimes -O3 is faster; sometimes code bloat causes cache misses and it's slower. For HPC, measure both.