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
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.
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)
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).
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.