Chapter 27 — Key Takeaways (Why Fortran Is Fast)

A one-page reference for compiler optimization, memory layout, and the no-aliasing advantage. The speed is the compiler's; your job is to hand it code it can optimize — and to read its reports.

The two things to memorize

  1. Inner loop over the first index. Fortran is column-major and the cache line is the unit of memory traffic, so the fastest loop nest sweeps down a column. This is the single largest performance factor you personally control.
  2. The compiler optimizes because Fortran forbids aliasing. The standard's guarantee that a written argument does not overlap a read one is the deepest reason Fortran's numerical code is fast — and why C needed restrict to catch up.

Optimization levels (gfortran)

Flag Adds Use for
-O0 nothing (fast compile, debuggable) debugging, with -g -fcheck=all
-O2 inlining, CSE, strength reduction — IEEE-clean production default
-O3 auto-vectorization, aggressive inlining hot numerical code (+ -march=native)
-Ofast -O3 + -ffast-math → reorders FP, can change results only after validating numerics
  • gfortran does not unroll loops at any -O level — request -funroll-loops explicitly.
  • -march=native uses every instruction your CPU supports (wider vectors); binary not portable.

Memory: the cache line

  • A cache line is 64 bytes = 8 real(dp); you never load one value, you load its line.
  • Main memory ≈ hundreds of cycles; cache ≈ a few. Most array code is memory-bound — the bottleneck is memory traffic, not arithmetic.
  • With the grain (inner loop over first index): each cache line used fully. Against the grain (inner over last index): one value per line, seven wasted → typically several-to-10× slower, same answer.
  • The 10× is an illustrative order of magnitude — measure your own; the direction is always the same.

The no-aliasing advantage

Fortran C
Default arguments assumed not to alias pointers may alias
Who promises the standard (automatic, everywhere) the programmer, per pointer, via restrict
Cost of the guarantee free — no runtime check conservative code, or a runtime overlap check + two loop versions
If violated undefined behavior (your responsibility) undefined behavior
  • Rule: never pass one array as both a written (intent(out)/inout) and a read (intent(in)) argument.
  • Aliasing a written argument is not a compile error — it is UB that can differ at -O0 vs -O3.

pure / elemental = optimization licenses

Attribute Promise The optimizer may then…
pure no side effects hoist invariant calls, share equal calls (CSE), reorder, delete unused, run in do concurrent
elemental scalar body, array-ready, auto-pure apply elementwise → a vectorization-shaped loop
  • Purity is enforced — a print or module-write inside pure is a compile error.
  • Habit: make small math helpers pure, scalar transforms elemental, whenever they honestly qualify.

Reading the optimization report

Command Shows
gfortran -O3 -fopt-info-vec file.f90 loops vectorized (optimized: loop vectorized using N byte vectors)
gfortran -O3 -fopt-info-vec-missed file.f90 loops not vectorized, with the reason
-fopt-info-optimized / -fopt-info-inline / -fopt-info-all applied opts / inlining / everything
  • Exact wording varies by gfortran version — read it for the yes/no, not the string.
  • On godbolt.org (gfortran): packed …pd (e.g. vaddpd) = vectorized; scalar …sd (addsd) = not.
  • Intel: -qopt-report; NVIDIA nvfortran: -Minfo. (Details in Chapter 30.)

Decision aid: is my loop fast?

  • Is the inner loop over the first index? If not, swap it. (biggest single win)
  • Are the output and input arrays distinct? If not, you have UB.
  • Is the hot loop pure (no I/O, no global writes, no impure calls)? If not, it won't vectorize.
  • Did -fopt-info-vec say it vectorized? If not, -fopt-info-vec-missed tells you why.
  • Is it memory- or compute-bound (arithmetic intensity, flop/byte)? Optimize the thing that limits you — memory traffic for low-intensity kernels (triad ≈ 0.08, stencil ≈ 0.4), flops for high (matmul).

Flags introduced

-O3 · -Ofast (and its -ffast-math danger) · -march=native · -funroll-loops · -fopt-info-vec · -fopt-info-vec-missed · -fopt-info-optimized

Numbers worth remembering

  • Cache line: 64 bytes = 8 real(dp).
  • Loop-order penalty: several× to ~10× (illustrative; measure it).
  • Vector width at -march=native: often 4 real(dp) (AVX, "32 byte vectors").
  • Pure-Python element loop vs Fortran: commonly 50–100× slower.

Project piece added this chapter

No new solver code — a measurement: run the stencil sweep in both loop orders (step_i_inner vs step_j_inner), confirm they give the bit-identical field (max |difference| = 0, maxval = 100 by the maximum principle), and time the gap. Timings are yours to measure; Chapter 28 makes the measurement rigorous, Chapter 29 closes the gap.