AI InterviewSearch…Search problems, topics, or pages…Ctrl KGet started
Skip to content

19 Performance and undefined behaviour / Checkpoint

Checkpoint — Performance and undefined behaviour

25 min · +25 XP · 3 exercises · 14 questions

Exercises0/3
Quiz · 70% to clear14 q

This checkpoint covers the whole module: the undefined behaviour catalogue and the well-defined detectors for it, the cost model of copies, allocations and calls, cache lines, struct padding and traversal order, what the optimiser does under the as-if rule, and how to measure without being lied to.

How it works. Fourteen questions and three programs. You need 70% on the questions and every program accepted to clear the module. You can retake it as often as you like; your best score counts.

Before you start, make sure you can answer these from memory:

  • Why does if (x + 1 < x) never detect an overflow at -O2, and what do you write instead?
  • What does passing a std::vector<int> by value cost, and by const&?
  • How many elements are moved over a thousand push_backs into a doubling vector, and what does reserve change?
  • What is the size of struct { char a; double b; char c; } on this platform, and what does reordering do?
  • Why does a column-major inner loop over a wide matrix load a cache line per element?
  • What does the as-if rule permit, and why does it make an unprinted benchmark result worthless?
  • Which statistic of a set of benchmark runs should you report, and why not the mean?

The three programs are an overflow-safe statistics report over long long values, an array-of-structs against struct-of-arrays cache-line audit built on the padding rule, and an operation counter that runs insertion sort and selection sort on the same input and reports the work each did. Every one of them counts; none of them times.

Common questions

Why does if (x + 1 < x) never detect an overflow at -O2?

Signed overflow is undefined behaviour, so the compiler assumes x + 1 cannot overflow and folds the test to false. Test before the operation instead: x > std::numeric_limits<int>::max() - 1, a wider type, or __builtin_add_overflow.

What is the size of struct { char a; double b; char c; }?

24 bytes on x86-64: seven bytes of padding after a align b to 8, and seven more after c round the size up to a multiple of 8. Reordering the members as double b; char a; char c; removes the interior padding and makes it 16.

Why is an unprinted benchmark result worthless?

Under the as-if rule the compiler only has to preserve observable behaviour, and a value that never reaches an output is not observable. Dead-code elimination can therefore remove the whole computation, and the benchmark times nothing at all.

C++ Performance Quiz: Undefined Behavior, Cache and Padding

Test your C++ with 14 questions and three programs on undefined behaviour, overflow checks, copy and allocation costs, cache lines, padding and benchmarks.

  • Course: C++ study plan
  • Module: Performance and undefined behaviour
  • Kind: Checkpoint — cleared at 70%
  • Reading time: 25 min
  • Runtime: Clang 18 · C++20

Checkpoint — Performance and undefined behaviour is the checkpoint that closes the Performance and undefined behaviour module: a graded quiz and whole-program exercises, passed at 70%.

Instructions

This checkpoint covers the whole module: the undefined behaviour catalogue and the well-defined detectors for it, the cost model of copies, allocations and calls, cache lines, struct padding and traversal order, what the optimiser does under the as-if rule, and how to measure without being lied to.

How it works. Fourteen questions and three programs. You need 70% on the questions and every program accepted to clear the module. You can retake it as often as you like; your best score counts.

Before you start, make sure you can answer these from memory:

  • Why does if (x + 1 < x) never detect an overflow at -O2, and what do you write instead?
  • What does passing a std::vector<int> by value cost, and by const&?
  • How many elements are moved over a thousand push_backs into a doubling vector, and what does reserve change?
  • What is the size of struct { char a; double b; char c; } on this platform, and what does reordering do?
  • Why does a column-major inner loop over a wide matrix load a cache line per element?
  • What does the as-if rule permit, and why does it make an unprinted benchmark result worthless?
  • Which statistic of a set of benchmark runs should you report, and why not the mean?

The three programs are an overflow-safe statistics report over long long values, an array-of-structs against struct-of-arrays cache-line audit built on the padding rule, and an operation counter that runs insertion sort and selection sort on the same input and reports the work each did. Every one of them counts; none of them times.

Common questions

Why does if (x + 1 < x) never detect an overflow at -O2?

Signed overflow is undefined behaviour, so the compiler assumes x + 1 cannot overflow and folds the test to false. Test before the operation instead: x > std::numeric_limits<int>::max() - 1, a wider type, or __builtin_add_overflow.

What is the size of struct { char a; double b; char c; }?

24 bytes on x86-64: seven bytes of padding after a align b to 8, and seven more after c round the size up to a multiple of 8. Reordering the members as double b; char a; char c; removes the interior padding and makes it 16.

Why is an unprinted benchmark result worthless?

Under the as-if rule the compiler only has to preserve observable behaviour, and a value that never reaches an output is not observable. Dead-code elimination can therefore remove the whole computation, and the benchmark times nothing at all.

Exercises

Overflow-safe statistics

Report statistics over long long values without ever computing a sum, difference or product that does not fit. Read n and n values (each fits in a long long). Print count n; if n is 0 print empty and stop. Otherwise print, one per line: min, max, range (max − min), sum, mean (the integer quotient sum / n, truncated toward zero) and squares (the sum of every value squared). Any figure that would overflow prints the word overflow in its place — the mean too, when the sum did. Use __builtin_add_overflow, __builtin_sub_overflow and __builtin_mul_overflow on long long operands; once an accumulation has overflowed it stays overflowed.

Input: n, then n integers. Output: count, then either empty or the six statistic lines.

4
-3 9 2 6

prints

count 4
min -3
max 9
range 12
sum 14
mean 3
squares 130

AoS against SoA: a cache-line audit

Audit the cache lines two layouts touch, built on the padding rule from lesson 3. Read a struct as m type names (char/bool 1, short 2, int/float 4, long/double/ptr 8; each aligns to its size, the struct rounds up to its largest alignment), then n (≥ 1) elements and the 0-based index hot of the member a hot loop reads. Lines are 64 bytes and both layouts start at offset 0.

  • Array of structs: element i's hot member starts at byte i * size + offset; count the distinct lines those bytes fall in (a member never straddles a line here, since every width divides 64 and every offset is a multiple of its width).
  • Struct of arrays: the hot member alone is a contiguous array of n × width bytes: ceil(bytes / 64) lines.
  • All fields: AoS is ceil(n × size / 64); SoA is the sum of ceil(n × width / 64) over every member.

Print struct size S hot offset O width W, hot field: aos A soa B, all fields: aos C soa D, and hot ratio A/B fixed to two decimals.

Input: m, m type names, n, hot. Output: four lines.

4
double double double float
32
3

prints

struct size 32 hot offset 24 width 4
hot field: aos 16 soa 2
all fields: aos 16 soa 14
hot ratio 8.00

Insertion against selection: the work each did

Run two sorts on the same input and report the work each did, counted, never timed. Read n integers. Insertion sort: for i from 1, take key = a[i] and walk j down from i; each evaluation of a[j - 1] > key is one comparison; each a[j] = a[j - 1] is one shift; stop when the comparison fails or j reaches 0, then store the key. Selection sort: for each i from 0 to n − 2, scan j from i + 1 to the end, one comparison per a[j] < a[best]; if the best index differs from i, std::swap and count one swap.

Print sorted: followed by the sorted values (one space before each), then insertion comparisons C shifts S, selection comparisons C swaps S, and cheaper insertion, cheaper selection or cheaper tie comparing comparisons + shifts against comparisons + swaps.

Input: n, then n integers. Output: four lines.

6
5 2 4 6 1 3

prints

sorted: 1 2 3 4 5 6
insertion comparisons 12 shifts 9
selection comparisons 15 swaps 3
cheaper selection

In this module: Performance and undefined behaviour

← Measuring — clocks, benchmarks that lie and the tools that tell the truth · The interview template — the contest idioms and what they cost →