Chapter 30 — Key Takeaways (Compiler Flags and Platform-Specific Optimization)

A one-page field guide to the flags that buy speed — and the ones that quietly change your answer.

The gfortran optimization ladder

Flag What it does Result-preserving?
-O0 No optimization (the default). Fast compiles, honest debugging. Yes
-O1 Cheap, always-safe optimizations. Yes
-O2 Strong, well-tested suite. The release default. Yes
-O3 Aggressive: more inlining, loop transforms, vectorization. Occasionally slower — measure. Yes
-Ofast -O3 + -ffast-math (+ -fno-protect-parens). Relaxes IEEE; can change results. NO
-march=native Compile for this CPU's instruction set (widest SIMD). Not portable to other CPUs. Yes (but may enable FMA → last-bit changes)
-flto Link-time optimization: inline/analyze across files. Pass at compile and link. Yes

The one rule to memorize: -O0 through -O3 change speed, not answers. Only -Ofast/-ffast-math can change the number your program prints.

The two builds (never one)

Flags Use it for
Development -g -O0 -fcheck=all -fbacktrace -ffpe-trap=invalid,zero,overflow Writing and debugging. Catches bounds/alloc/NaN errors.
Release -O3 -march=native -flto (or a safe -O2) Real runs and all timing you report. Checks removed.

Never time a -fcheck=all build — the run-time checks can slow it several-fold, so you measure the checks, not your algorithm. Remove -fcheck=all for production.

Cross-compiler flag translation

Idea gfortran Intel ifx/ifort NVIDIA nvfortran
Aggressive optimize -O3 -O3 -fast
Tune for host CPU -march=native -xHost (part of -fast)
Whole-program / LTO -flto -ipo
Optimization report -fopt-info -qopt-report -Minfo
IEEE-strict FP (default) -fp-model precise -Kieee

The cross-compiler trap: Intel's default floating-point model is relaxed (-fp-model fast), so ifx can print different numbers than gfortran for the same source. Add -fp-model precise for agreement. (ifort is deprecated in favor of the LLVM-based ifx. Verify vendor flags against your version.)

Terms introduced

  • -Ofast / relaxed IEEE-O3 plus -ffast-math; permits reassociation and ignores parentheses. Can turn (1e20 + -1e20) + 1 from 1.0 into 0.0. Validate before trusting; connects to Chapter 20.
  • -march=native — build for the exact host CPU's instruction sets. Real speed on vectorizable code; the binary may crash (illegal instruction) on a CPU that lacks those instructions.
  • -flto / link-time optimization — defer optimization to link time so the whole program is visible; enables cross-file inlining. Pass it at both compile and link.
  • Profile-guided optimization (PGO) — instrument → run on representative input → rebuild with the profile. Helps branchy code; little help for a branch-free stencil.
  • Reproducible build — one whose compiler, version, flags, architecture, and libraries are recorded so the result can be reconstructed and trusted.

Reproducibility: make the binary record itself

use, intrinsic :: iso_fortran_env, only: compiler_version, compiler_options
print '(2a)', 'compiler: ', compiler_version()   ! e.g. "GCC version 13.2.0"
print '(2a)', 'options : ', compiler_options()    ! the exact flags used

Record with every timing: compiler + version, the full flag list, and the machine/CPU (especially if -march=native was used). A number without its recipe is not a measurement.

Profile-guided optimization (gfortran, three steps)

$ gfortran -O3 -fprofile-generate prog.f90 -o prog_instr   # 1. instrument
$ ./prog_instr < representative_input                       # 2. run on REAL data (writes .gcda)
$ gfortran -O3 -fprofile-use      prog.f90 -o prog_pgo      # 3. rebuild using the profile

Worth it when: profiling shows a branchy hot spot, the run is long/repeated, and you can automate it. Skip it for tight numerical kernels.

Which flag / when — decision aid

Situation Reach for
Writing / debugging code -g -O0 -fcheck=all -fbacktrace
A safe, fast release -O2
A vectorizable kernel, build == run machine -O3 -march=native -flto
Squeezing branchy code, long runs add PGO (-fprofile-generate/-use)
"Bug appears only at -O3" rebuild -O0 -fcheck=all -finit-real=snan — it's your undefined behavior
Shipping to unknown CPUs drop -march=native; pick a conservative baseline -march=
Matching results across compilers set optimization and FP model explicitly (-fp-model precise on Intel)

Common pitfalls

  • -Ofast changes results silently — no warning; can break compensated sums and cancellation-prone code. Validate against a strict build; record that you used it.
  • -march=native in a shipped Makefile/fpm.toml — builds a binary that may not run elsewhere. Put it behind an opt-in release profile.
  • Timing a -fcheck=all build — measures the safety checks, not your code (the classic false slowdown).
  • Comparing a debug baseline to a release build — conflates "removed checks" with "optimized." Compare checks-off vs checks-off.
  • Forgetting -flto at the link step — it must appear at compile and link.
  • Assuming compilers agree by default — Intel's relaxed FP default drifts the last digits.

Numbers & rules worth memorizing

  • gfortran's default with no -O flag is -O0 (unoptimized).
  • -flto goes on both the compile and link commands.
  • A typical -O0-O3 -march=native -flto speedup on a vectorizable stencil is on the order of a few ×illustrative, machine-dependent (Tier 2); always measure and record.
  • -Ofast = -O3 + -ffast-math; if you want -O3's speed without the numeric risk, use -O3 alone.

Heat-solver piece added this chapter

Not code — discipline. Build the finished solver two ways (-O0 baseline, -O3 -march=native -flto release), confirm the field checksum is unchanged (600.00 for the 5×5 two-step solve — flags buy speed, not a different answer), measure the illustrative speedup, and write the flags into a BUILD.md beside the result. That coupling of result + flags + machine is the seed of the reproducible project in Chapter 37.