Chapter 30 Exercises: Compiler Flags and Platform-Specific Optimization

These exercises are about the command line, so many ask you to build the same code several ways and compare — the point of the chapter. Work them at a real terminal with gfortran; where an exercise names Intel or NVIDIA flags, you can reason about them from the tables in §30.2 even without those compilers installed.

Difficulty tiers. ⭐ builds a single idea; ⭐⭐ combines two or three and asks you to judge; ⭐⭐⭐ is open-ended and closer to real practice. Solutions to -marked and odd-numbered problems are in appendices/answers-to-selected.md; the compilable ones are in code/exercise-solutions.f90.

A discipline for every timing in this set: whenever you record a time, record the compiler version, the full flag list, and the machine beside it. An unlabeled time is not a measurement.


Part A — Type, Compile, and Run (predict first)

1. ⭐ † Compile example-01-opt-levels.f90 at -O0, -O2, and -O3 -march=native. Before running, predict whether the three printed values will match. Run all three and confirm. State the one-sentence rule your result illustrates.

2. ⭐ Type example-02-ofast-reassoc.f90 and build it at -O2. Predict the output by tracing (a + b) + c by hand with a = 1e20, b = -1e20, c = 1. Run and confirm.

3. ⭐⭐ † Now build the same example-02 file at -Ofast. Does your gfortran actually change the answer to 0.00, or does it still print 1.00? (Either is possible.) Explain what -Ofast is permitted to do here, regardless of what your particular compiler chose to do.

4. ⭐ Build and run example-03-build-provenance.f90. Read the options: line it prints back to you. Then rebuild adding -flto and confirm the options: line now shows it. Explain in one sentence why this is useful.

5. ⭐⭐ † Predict the output of this program, then compile and run it at -O2:

program guess
  use, intrinsic :: iso_fortran_env, only: dp => real64
  implicit none
  real(dp) :: s
  integer  :: i
  s = 0.0_dp
  do i = 1, 10
    s = s + 0.1_dp
  end do
  print '(a, l1)', 's == 1.0? ', (s == 1.0_dp)
end program guess

Is the answer a bug in optimization, or something else? Which chapter explains it?


Part B — Port It (translate a recipe)

6. ⭐ A colleague on an Intel cluster sends you their build line: ifx -O3 -xHost -ipo mysolver.f90 -o mysolver. Translate it, flag for flag, into the gfortran equivalent you would use on your own machine.

7. ⭐⭐ † You have a gfortran release recipe -O3 -march=native -flto that you validated carefully. A teammate will build the identical source with ifx. Give them the ifx recipe that (a) matches your optimization intent and (b) matches your IEEE-strict floating-point behavior. Name the extra flag they need that has no gfortran counterpart in your line, and say why.

8. ⭐⭐ Someone hands you a pip-installed NumPy that they claim is "as fast as Fortran." Explain, in terms of this chapter's flags, one concrete reason a from-source, -march=native build of the same numerical kernel might beat the distributed wheel — and one reason the wheel is built the way it is.


Part C — Find the Bug (build edition)

9. ⭐ † A build fails at the link step:

$ gfortran -std=f2018 -O3 -flto -c solver.f90
$ gfortran -std=f2018 -O3 solver.o main.f90 -o app

The linker produces confusing warnings and the result runs oddly. What did the second command forget, and what is the fix?

10. ⭐⭐ A binary built and tested fine on the developer's laptop. Copied to an older compute node with the same OS, it dies immediately with Illegal instruction (core dumped) before printing anything. No source changed. Name the most likely flag responsible and give two ways to fix the deployment.

11. ⭐⭐ † A teammate reports the solver is "1000× slower than the paper claims" and wants to rewrite it in C++. You look at their build line: gfortran -O0 -g -fcheck=all -fbacktrace solver.f90 -o solver. Diagnose the real problem in one sentence and give the corrected timing command.

12. ⭐⭐⭐ A code "gives the right answer at -O0 but wrong answers at -O2." Your colleague is certain it is a gfortran bug. List the three latent bugs in their code that most commonly cause this, and give the exact one-line development build you would use to expose each.


Part D — Modernize the Build

13. ⭐⭐ † This fragment of an old Makefile is not portable:

FC = ifort
FFLAGS = -O3 -xHost -ipo -fp-model fast

Rewrite it so the same project builds correctly under both gfortran and Intel, with an explicit, IEEE-strict floating-point setting on each. (Pseudo-Make or prose is fine; the point is the flag mapping and the two profiles.)

14. ⭐⭐ Take the single-recipe build habit and split it into two named profiles — debug and release — listing the exact gfortran flags in each, and write one sentence on when you switch between them.


Part E — Design It (extend the solver)

15. ⭐⭐ † Add a --version-style provenance banner to your heat solver: on startup it prints compiler_version() and compiler_options() so every run's log records its own build. Sketch the code and say where in the program flow it belongs.

16. ⭐⭐ Write a BUILD.md for your heat-solver/ directory. It must let a stranger reproduce your fastest validated binary exactly: list the compiler and version, the full release flag line, the machine's CPU (explain why the CPU matters here), and the linked library versions.

17. ⭐⭐⭐ Design a three-command profile-guided-optimization build for the solver. State which part of the solver you expect PGO to help (and which part it will not), what "representative input" means for a heat run, and how you would measure whether PGO actually helped rather than assuming it did.

18. ⭐⭐⭐ † Your group must publish a result that is bit-for-bit reproducible across two clusters with different CPUs. Which flags from this chapter must you avoid or pin, and what do you trade away to get exact reproducibility? Reference the FMA point from §30.1.


Part F — Back of the Envelope

19. ⭐ † A generic build vectorizes your real(dp) stencil 2 numbers at a time (128-bit). An -march=native build on an AVX-512 machine does 8 at a time (512-bit). Ignoring all other effects, what is the ceiling on the speedup from wider vectors alone, and why is the real speedup usually much less? (Recall the memory-bound vs compute-bound distinction from Chapter 28.)

20. ⭐⭐ Your -flto release link takes 90 seconds versus 12 seconds without it, but the resulting binary runs a 6-hour simulation 8% faster. Was -flto worth it? Show the arithmetic and state the general rule your numbers imply.

21. ⭐⭐ † PGO adds a full instrument-run-rebuild cycle to your build (say, 3× the build time) and yields a 4% runtime improvement. For a kernel you run once, is it worth it? For one you run 10,000 times in a parameter sweep? Give the crossover reasoning, not just a yes/no.

22. ⭐⭐ You measure -O0 at 40.0 s and -O3 -march=native -flto at 6.5 s for the same run on the same machine. Report the speedup the honest way, including the two things that must accompany the number, and say why quoting "6.2×" alone is not yet a reproducible claim.


Part G — Interleaved (reach back)

23. ⭐⭐ † (with Chapter 20) Explain precisely why -Ofast can change a sum but never changes an integer count. Which property of IEEE floating-point arithmetic is -ffast-math exploiting, and why does that property not apply to integers?

24. ⭐⭐ (with Chapter 27) Chapter 27 said the compiler optimizes aggressively because Fortran forbids argument aliasing. Explain how that guarantee is what makes raising the -O level safe — i.e., why the reorderings at -O3 cannot silently corrupt results the way they might in C without restrict.

25. ⭐⭐ † (with Chapter 29) You reordered a loop for column-major access (Chapter 29) and it is still slow at -O0. A teammate says "just add -O3, that will fix the memory pattern." Are they right? Explain the division of labor between source-level optimization and flag-level optimization.

26. ⭐⭐ (with Chapter 28) Why is timing a -fcheck=all build one of the most common ways a beginner produces a misleading benchmark? Tie your answer to Chapter 28's benchmarking methodology.

27. ⭐⭐⭐ (with Chapters 2 and 37) You are setting up continuous integration for the solver. Describe a CI matrix that builds the code under two compilers and two profiles, and explain what each cell catches that the others miss. Why is "builds under gfortran only" a portability risk?

28. ⭐⭐⭐ † (synthesis) Write a one-page "flags policy" for a small research group: the development build, the release build, the rule for -Ofast, the rule for -march=native on shipped binaries, and the recording requirement for any published number. Justify each line in a sentence.


When you have finished Part A and at least one problem from each of Parts C, E, and F, you can build any Fortran project two ways with confidence and defend every flag you chose. Check your solutions to the problems against appendices/answers-to-selected.md.