Chapter 30 Self-Check Quiz: Compiler Flags and Platform-Specific Optimization
Twenty questions to test whether you can choose flags deliberately rather than by superstition. Aim for 16 / 20. Answers and a topic map are at the end; a "true/false" answer is only correct if your one-line justification is right too.
Multiple choice
1. Which optimization level is gfortran's default when you pass no -O flag at all?
- (a) -O1
- (b) -O2
- (c) -O0
- (d) -Ofast
2. Which flag is the odd one out because it can change the numerical result of your program?
- (a) -O2
- (b) -O3
- (c) -Ofast
- (d) -march=native (choose the most dangerous for results)
3. -march=native primarily buys speed by:
- (a) inlining functions across source files
- (b) enabling the build machine's widest SIMD instruction set
- (c) relaxing IEEE floating-point rules
- (d) recording the compiler version in the binary
4. -flto must be supplied:
- (a) only at the compile step
- (b) only at the link step
- (c) at both the compile and link steps
- (d) only when using Intel ifx
5. The Intel ifx flag that corresponds to gfortran's -march=native is:
- (a) -ipo
- (b) -xHost
- (c) -qopt-report
- (d) -fp-model precise
6. The Intel ifx flag that corresponds to gfortran's -flto is:
- (a) -xHost
- (b) -O3
- (c) -ipo
- (d) -fast
7. Profile-guided optimization is most likely to help: - (a) a tight five-point stencil loop with no branches - (b) a branch-heavy code that chooses different logic per grid cell - (c) a program that is entirely memory-bound - (d) any program equally, since it always adds speed
8. Which pair is the correct development build, per §30.3?
- (a) -O3 -march=native -flto
- (b) -O2 -flto
- (c) -g -O0 -fcheck=all -fbacktrace
- (d) -Ofast -fcheck=all
9. A binary built with -march=native on a new laptop dies with "illegal instruction" on an older
server (same OS). The cause is:
- (a) a compiler bug
- (b) the server's CPU lacks instructions the binary was compiled to use
- (c) -flto was not passed at link time
- (d) the floating-point model differs
10. Which two standard iso_fortran_env functions let a program report its own build recipe?
- (a) command_argument_count and get_command
- (b) compiler_version and compiler_options
- (c) system_clock and cpu_time
- (d) huge and tiny
True / False (justify in one line)
11. True or false: Raising the level from -O0 to -O3 can, in ordinary IEEE-compliant code, change
the value your program prints.
12. True or false: -fcheck=all should be included in the build you use to report timing numbers.
13. True or false: If a program gives the right answer at -O0 but a wrong answer at -O3, the most
likely cause is a bug in the gfortran optimizer.
14. True or false: Two builds of the same source that differ only in whether fused multiply-add (FMA) is used are guaranteed to produce bit-identical floating-point results.
15. True or false: Hardcoding -march=native into a Makefile you publish for others is a good idea
because it always makes the code faster.
Short answer
16. In one sentence each, state what -O2, -O3, and -Ofast are for and which one you must validate
before trusting.
17. Why is a performance number reported without its compiler, flags, and machine considered not reproducible? Give the specific mechanism (name at least one flag whose omission would change the number).
18. Explain, referencing Chapter 20, why -Ofast can change the result of a floating-point sum but
never changes the result of an integer count.
What does this print?
19. At -O2, what does this print, and would -O3 change it?
program q19
use, intrinsic :: iso_fortran_env, only: dp => real64
implicit none
real(dp) :: v(6) = [2.0_dp, 4.0_dp, 6.0_dp, 8.0_dp, 10.0_dp, 12.0_dp]
print '(a, f7.2)', 'mean = ', sum(v) / size(v)
end program q19
20. At -O2, what does this print? (Trace the parentheses by hand.)
program q20
use, intrinsic :: iso_fortran_env, only: dp => real64
implicit none
real(dp) :: big, small, r
big = 1.0e16_dp
small = 1.0_dp
r = (big + small) - big
print '(a, f5.1)', 'r = ', r
end program q20
Answer Key
| # | Answer | One-line rationale |
|---|---|---|
| 1 | c | With no -O, gfortran does not optimize; -O0 is the default. |
| 2 | c | -Ofast adds -ffast-math, which relaxes IEEE and can change results; -O2/-O3 preserve them. |
| 3 | b | It targets the host CPU and enables its widest SIMD (e.g. AVX-512). |
| 4 | c | LTO changes compilation and linking, so it is needed at both. |
| 5 | b | -xHost tunes for the host CPU, like -march=native. |
| 6 | c | -ipo is Intel's interprocedural (whole-program) optimization. |
| 7 | b | PGO improves branch layout and inlining; branchy code benefits, a branch-free stencil barely does. |
| 8 | c | Development = debuggable and guarded: -g -O0 -fcheck=all -fbacktrace. |
| 9 | b | -march=native emits instructions the older CPU does not implement. |
| 10 | b | compiler_version() and compiler_options() return the build recipe. |
| 11 | False | -O0..-O3 preserve results; only -Ofast/-ffast-math can change them. |
| 12 | False | -fcheck=all adds run-time checks that slow the code several-fold; timing it measures the checks. |
| 13 | False | Almost always undefined behavior in your code (uninitialized variable, out-of-bounds) that -O3 exposes. |
| 14 | False | FMA does one rounding instead of two, so an FMA build can differ in the last bit. |
| 15 | False | A published -march=native binary/recipe may not run (or build correctly) on other CPUs; it is a portability trap. |
| 16 | — | -O2: safe release default; -O3: aggressive (measure it); -Ofast: fastest but relaxes IEEE — validate -Ofast. |
| 17 | — | The same source runs at many speeds/answers depending on flags (-Ofast, -march=native) and CPU; omit them and the number cannot be reconstructed or compared. |
| 18 | — | -ffast-math exploits that FP addition is non-associative (rounding each step); integer arithmetic is exact and associative, so reassociation cannot change an integer count. |
| 19 | — | mean = 7.00 (sum 42 / 6 = 7); -O3 does not change it (result-preserving). |
| 20 | — | r = 0.0 — the small is absorbed: 1e16 + 1 rounds back to 1e16, so - big leaves 0. The parentheses (honored at -O2) make this well-defined. |
Note on Q20 (a Chapter 20 lesson in disguise):
real64is exact for integers only up to $2^{53} \approx 9.0\times10^{15}$. At $10^{16}$ the gap between representable doubles is $2$, so $10^{16}+1$ sits exactly halfway between $10^{16}$ and $10^{16}+2$; round-to-nearest-even picks $10^{16}$. Thusbig + smallgives backbig, andr = 0.0— the+1vanished entirely (absorption). This is why order and grouping matter: at-O2the parentheses are honored and the answer is a well-defined0.0. Under-Ofast, a compiler that "simplifies"(big + small) - bigtosmallcould instead print1.0— the same source, a different answer. That contrast is the point of the chapter.
Topics to review by question
| Questions | Review |
|---|---|
| 1, 2, 8, 11, 16 | §30.1 the -O ladder; §30.3 dev vs release |
| 3, 9, 15 | §30.1 -march=native; §30.5 portability |
| 4, 6 | §30.1 -flto; §30.2 Intel -ipo |
| 5, 6, 14 | §30.2 other compilers; FMA note in §30.1 |
| 7, 17 | §30.4 PGO and reproducible builds |
| 10, 17 | §30.4 compiler_version/compiler_options |
| 12, 13 | §30.3 dev vs release; the "optimization exposes bugs" rule |
| 18, 19, 20 | §30.1 -Ofast; Chapter 20 floating point |