Topic 0 › Chapter 0.9

How CPUs fight latency

Pipelines, out-of-order, prefetching: the machinery that hides memory latency, until a dependency chain blocks it.

You already know · 0.4 The memory wall · 0.5 Caches

The problem › 450 lost cycles

The core is faster than its food supply

Put the memory wall from 0.4 in a single core's terms. Our reference CPU runs its P-cores at about 5 GHz (one cycle every 0.2 ns), while a trip to DRAM costs roughly 90 ns. Miss every cache on one load and the core has ~450 cyclesin which, naively, there is nothing to do. Bakhvalov puts it at "more than three hundred clock cycles" on current hardware. From the CPU's perspective, a last-level cache miss feels like a very long time.

1Bakhvalov 2e Ch. 8 · p. 193

Caches (0.5) make that miss rare. This chapter is about the other half of the fight: the machinery that keeps the core busy anyway, and the one enemy that machinery cannot beat. Everything here stays qualitative; you never program these mechanisms, but you feel them in every benchmark you'll run.

The mechanism › Four tricks, one budget

Machinery that manufactures overlap

Every trick a CPU plays is the same bet stated four ways: find work that doesn't depend on the thing we're waiting for. The hardware view: a core splits into an in-order frontend that fetches x86 instructions and decodes them into fixed-length micro-operations (µops), and an out-of-order backend that executes those µops whenever their inputs are ready. CS:APP calls the two halves the instruction control unit and the execution unit.

2CS:APP 3e §5.7.1 · pp. 554–5563Bakhvalov 2e §3.8 · p. 64

Pipelining. An instruction passes through stages (fetch, decode, execute, memory, write-back), and every stage works on a different instruction each cycle, like laundry overlapping in washer and dryer. Even single arithmetic units pipeline internally: a floating-point multiply takes 5 cycles to finish, but a fully pipelined multiplier starts a new multiply every cycle.

4Bakhvalov 2e §3.2 · p. 395COD RISC-V Ch. 46CS:APP 3e §5.7.2, Fig. 5.12 · p. 559

Superscalar issue.There isn't one pipeline: the backend of our reference core is 6-wide, dispatching µops to 12 execution ports each cycle; mainstream cores in 2024 issue 6–9 instructions per cycle.

7Bakhvalov 2e §3.3.2, §3.8 · pp. 44, 64–68

Out-of-order execution.When a load misses, the core doesn't freeze in program order. Decoded µops wait in a reorder buffer (512 entrieson our reference core's Golden Cove), and any instruction whose inputs are ready executes, regardless of program position. Results are then retiredstrictly in order so the program can't tell. The miss still takes 90 ns; it just stops being the only thing happening.

8Bakhvalov 2e §3.3.1, §3.8.2 · pp. 42–43, 66–67

Prefetching. The memory system watches your address stream. Walk memory sequentially (the row-major sweep from 0.6) and the hardware prefetcher recognizes the pattern and fetches the next lines beforeyou ask, converting would-be misses into hits. This is exactly why "access data sequentially" is rule one of cache-friendly code.

9Bakhvalov 2e §8.1.1 · p. 194

Speculation.A branch would stall the frontend until its condition resolves, so the branch prediction unit guesses the outcome and the core barrels ahead, executing the predicted path without committing results until the guess is confirmed. Modern predictors routinely exceed 95% accuracy; the catch is the misprediction penalty (measured at ~19 cycles on CS:APP's reference machine) paid to flush the speculative work and refill the pipeline.

10Bakhvalov 2e §3.3.3–3.3.4 · pp. 44–4711CS:APP 3e §5.11.2 · pp. 585–586

SourcesCS:APP 3e §5.7 · pp. 553–561Bakhvalov 2e §3.2–3.3, §3.8 · pp. 39–47, 64–71

The limit › Dependency chains

Where the machinery dies

All four tricks need the same raw material: independent work. Register renaming even manufactures some: the 16 architectural registers your assembly names are aliases onto roughly 300 physical ones, which erases the false collisions (two instructions merely reusing the same register name). But a true value-flows-to-value dependency, what Bakhvalov calls a dependency chain, survives renaming by definition. Only genuine data flow constrains the machine.

12Bakhvalov 2e §3.3.1.2 · p. 4313CS:APP 3e §5.7.1 · p. 558

Give the core a loop where iteration i+1 needs nothing from iteration i (summing an array) and it keeps dozensof loads in flight at once: CS:APP's Haswell had two load units holding up to 72 pending reads each; Golden Cove issues up to three loads per cycle with 16 line-fill buffers and a 48-entry queue of outstanding uncore requests. That is memory-level parallelism, and it is the real reason streaming code hides DRAM latency.

14CS:APP 3e §5.12 · p. 59015Bakhvalov 2e §3.8.3 · pp. 68–69

Now walk a linked list: p = p->next. The address of the next load is insidethe current one. Until this miss returns, the next load cannot even be named: there is nothing to reorder, nothing to prefetch, nothing to speculate about. The chain serializes memory latency: you pay per link, in full, single file. CS:APP turns this into a measurement technique: a linked-list traversal's time per element exactly equals the load latency (their list_len clocks 4.0 cycles per element, the documented L1 hit latency), because the loads form one unbroken dependency chain. Parallelism is where latency-hiding lives, and a chain is where parallelism dies.

16CS:APP 3e §5.12.1 · pp. 590–591

Predict first

Sum one million ints in an array, versus follow one million pointers through a shuffled linked list (same total data, all misses to DRAM). Roughly how much slower is the pointer chase?

interactive · coming soon

DependencyChain: the OoO window visualizer

Planned interactive: a stream of instructions enters an out-of-order window; watch independent loads overlap their latencies, then rewire the same loop into a pointer chain and watch the window drain to a single crawling dependency.

The number › The price of the machinery

How much work hides one miss?

Compute the number › stall arithmetic on the reference machine

DRAM latency90 ns
core clock5 GHz (0.2 ns/cycle)
cycles per miss90 × 5 = 450
backend width (Golden Cove, 6 µops/cycle)450 × 6 ≈ 2,700
Result≈ 2,700 instruction slots to fill per miss

To fully hide one DRAM miss, a 6-wide core would need thousands of independent instruction slots in flight, from a reorder buffer of 512. The machinery is heroic and it is still not enough; that gap is why the miss rate (0.5, 0.6) remains the number that rules everything.

17Bakhvalov 2e §3.8.2 · pp. 66–67

Now look at a die photo of a modern CPU with this arithmetic in mind. The actual arithmetic units are a sliver. The rest (megabytes of cache, the 512-entry window and its schedulers, predictors with 12K-entry branch target buffers, prefetchers) is all latency-fighting machinery, spent per thread to make one stream of dependent instructions run fast.

18Bakhvalov 2e §3.8.1 · p. 65

Hold that image. Topic 1 opens with the other answer to the same 450-cycle problem: strip out the machinery, and when a thread stalls, just run a different thread. Thousands of them. That single design swap is the entire difference between a CPU and a GPU (1.1).

The full story › what "scan ahead" really means

Author-depth, not exam material. On Golden Cove, the P-core of our reference machine, the frontend decodes 6 x86 instructions per cycle (with a 4K-entry µop cache bypassing decode for hot code) into a 144-µop queue; the backend allocates 6 µops per cycle into the 512-entry reorder buffer, where the register alias table renames the 16 architectural integer registers onto ~300 physical ones. A ~200-entry reservation station dispatches up to 6 ready µops per cycle across 12 execution ports, and the ROB retires up to 8 per cycle, strictly in program order, so exceptions and debuggers see a sane world. The window is the budget for latency hiding, and a pointer chase is precisely the workload that makes a 512-entry window behave like a 1-entry one.

19Bakhvalov 2e §3.8.1–3.8.2 · pp. 64–6720CS:APP 3e §5.7.1 · pp. 557–558

A CPU hides memory latency with per-thread machinery (pipelines, a 512-deep out-of-order window, prefetchers, 95%-accurate speculation), and it works exactly as long as independent work exists to overlap. True dependency chains defeat it, die area pays for it, and the GPU's answer to the same problem is the subject of Topic 1.