Fewer misses: size, blocks, ways, victims, prediction, prefetching and the compiler

After this lesson you will name, for each of the eight techniques of the notes' miss-rate family, the kind of miss it removes and the price it pays — and compute when a bigger block or a more associative cache is slower, not faster.

One. A 4 KB cache with 64-byte blocks misses less often than the same cache with 32-byte blocks — 7.00 % of accesses against 7.24 % — and yet its average access is slower. Two. Turning a 512 KB direct-mapped cache into a 2-way set-associative one lowers its miss rate from 0.8 % to 0.7 %, and makes it slower too. Both with the textbook's numbers (Hennessy & Patterson).

Three. In a loop over a 5000 × 100 array of 4-byte words, swapping the two for lines cuts the misses sixteen-fold with 64-byte blocks. Same cache, same data, not one transistor changed.

The first two look like contradictions, the third like a free lunch. One sentence explains all three; you will be able to write it at the end of the lesson.

One metric, three terms: what a knob really buys

Every technique in this lesson is judged by the formula of lesson 22, the first line under "Memory Hierarchy" on the example formula sheet of the course page:

\[ \text{AMAT} = \text{HT} + \text{MR} \times \text{MP} \]
HT
hit time: what every access pays, in cycles
MR
miss rate: the fraction of accesses that miss
MP
miss penalty: the extra cycles of one miss

The notes sort the ways to improve it into three families, one per term (p. 321): fewer misses (this lesson, pages 323–341), cheaper misses (lesson 26), faster hits (lesson 28). Most knobs here aim at one of the three C's of lesson 24: compulsory misses (the first touch of a block), capacity misses (the cache cannot hold the working set), conflict misses (too many busy blocks share one set).

Turning a knob lowers the miss rate and almost always pushes up another cost — the hit time, the penalty or the memory bandwidth. So the question is never "does it cut misses?" but "does it cut AMAT?".

A knob that lowers the miss rate pays only if the misses it saves, times the penalty, outweigh what it adds to the other terms.

The first knob: a bigger cache

A bigger cache holds more blocks, so fewer of them are thrown out and fetched again: it removes capacity misses (p. 323). It does nothing for compulsory misses — the first touch of a block misses in any cache — and the returns diminish: "doubling the cache size doesn't halve the miss rate" (p. 323). In the textbook's measurements a direct-mapped cache misses 9.8 % of its accesses at 4 KB and 4.2 % at 32 KB: eight times the capacity, the miss rate divided by 2.3.

The price is in the first term. A bigger array has longer wires, a bigger decoder and a bigger tag store, so the hit time grows, and so do area, power and cost (the table on p. 323). Every access pays the hit time, only the misses pay the penalty — so the L1 stays small and fast, one or two cycles at the processor's clock, and the capacity goes into the second and third levels, which "absorb the remaining misses" (p. 323).

Bigger blocks: fewer first touches, then a U-turn

Start with Example 9 of the notes (p. 324), taking a as 100 four-byte integers that begin at a block boundary:

for (i = 0; i < 100; i++)
    sum += a[i];

Every element is touched once, so every miss is compulsory, and the misses are simply the blocks the array spans: 25 with 16-byte blocks, 7 with 64-byte blocks. A bigger block preloads the neighbours of the word you asked for: spatial locality, turned into fewer misses.

a[0] … a[15], 4 bytes each miss miss miss miss a[0] a[15] 1 miss, then 3 hits

Before: 16-byte blocks — 4 misses in 16 accesses

a[0] … a[15], 4 bytes each miss a[0] a[15] 1 miss, then 15 hits

After: 64-byte blocks — 1 miss in 16 accesses

Fig. 1. The first sixteen accesses of sum += a[i]. Orange: the access that misses and brings the block in; blue: accesses served by a block already fetched. Four times larger blocks, a quarter of the compulsory misses — 25 become 7 for the whole loop.

Now hold the cache size fixed and keep growing the block. A 4 KB cache has 256 lines of 16 bytes, but only 16 lines of 256 bytes. With so few lines, blocks that are still needed are thrown out to make room: conflict and capacity misses rise, and the curve of miss rate against block size turns upward — the U-shaped curve of p. 325. Large blocks also carry bytes nobody uses, wasting bandwidth and energy when spatial locality is weak (the table on p. 324). In practice 16 to 64 bytes are common, and instruction caches, fetched in sequence, tolerate larger blocks than data caches (p. 325).

The penalty grows with the block, so AMAT turns earlier

A miss costs a fixed latency before the first bytes arrive, plus the transfer of the whole block. The textbook's model: 80 cycles, then 16 bytes every 2 cycles.

\[ \text{MP}(B) = 80 + 2 \cdot \frac{B}{16} \ \text{cycles} \]
\(B\)
block size in bytes: 82, 84, 88, 96, 112 cycles for 16, 32, 64, 128, 256 bytes

Put it next to the miss rates the textbook measured for a 4 KB cache, with a 1-cycle hit time:

4 KB cache: the miss rate is lowest at 64 bytes, AMAT at 32 bytes.
Block sizeMiss rateMiss penalty (cycles)AMAT = 1 + MR × MP
16 B8.57 %828.03
32 B7.24 %847.08
64 B7.00 %887.16
128 B7.78 %968.47
256 B9.51 %11211.65

That is fact one of the hook: from 32 to 64 bytes the miss rate drops by 0.24 points, but every remaining miss costs 4 cycles more. It is not an accident of these numbers — the optimum for AMAT can never lie to the right of the optimum for the miss rate:

  1. Only two terms depend on the block size \(B\); the hit time does not:

    \[ \begin{aligned} \text{AMAT}(B) &= \text{HT} \\ &+ \text{MR}(B) \times \text{MP}(B) \end{aligned} \]
  2. Call \(B^*\) the block with the lowest miss rate, and take any larger block \(B \gt B^*\). Its miss rate is not lower, by the choice of \(B^*\):

    \[ \text{MR}(B) \ge \text{MR}(B^*) \]
  3. Its penalty is higher, because more bytes must travel:

    \[ \text{MP}(B) \gt \text{MP}(B^*) \]
  4. Neither factor shrank and the penalty grew, so the product grew, and with it AMAT. No block larger than \(B^*\) beats \(B^*\): the AMAT optimum is \(B^*\) or smaller.

    \[ \text{AMAT}(B) \gt \text{AMAT}(B^*) \]
  5. How much smaller depends on the memory: when the latency dominates, the penalty hardly grows with the block and the two optima meet; when the transfer dominates, small blocks win. Move that balance in the widget of the next section.

More ways: conflict misses out, hit time up

Example 10 (p. 327): a loop reads a[i], b[i] and c[i], and the three blocks map to the same set 5. In a direct-mapped cache each evicts the one read before it — three misses per iteration, while the rest of the cache may sit empty. In a 4-way set-associative cache (three ways would do) the three live in set 5 together and stop evicting each other.

Associativity is the number of places a block may occupy: 1 for direct-mapped, n for n-way, every line for fully associative (placement is lesson 23). Raising it removes conflict misses and nothing else: compulsory and capacity misses stay the same (p. 326), and a fully associative cache has, by definition, no conflict misses at all.

Two rules of thumb

The 2:1 cache rule (p. 327): a direct-mapped cache of size N has about the same miss rate as a 2-way set-associative cache of size N/2 — associativity can be traded for size. And the gain flattens fast. The textbook's 4 KB cache misses 9.8 % direct-mapped, 7.6 % 2-way, 7.1 % 4-way and 7.1 % 8-way: most of the gain comes with the first doubling — the notes' sweet spot is 2- or 4-way (p. 327) — and 8-way is, for practical purposes, as good as fully associative.

The price is paid at the multiplexer

A direct-mapped cache has one candidate line, so it can send the data towards the processor while the tag comparison is still running; if the comparison fails, the data is simply dropped. An n-way cache reads n candidates and compares n tags in parallel — but it can pick the data only after a comparator has said which way hit. That way-select multiplexer sits on the path of every hit, and reading n ways at once costs power on every access (the table on p. 327).

The textbook prices it as a slower clock: × 1.36 for 2-way, × 1.44 for 4-way, × 1.52 for 8-way, with a 25-cycle penalty. For a 4 KB cache the saved misses win: AMAT falls from 3.45 cycles direct-mapped to 3.22 at 4-way. For a 512 KB cache there are almost no conflicts left to remove, and the hit time wins: 1.20 cycles direct-mapped, 1.54 for 2-way — fact two of the hook.

"Associativity lowers the miss rate, so it lowers AMAT." Only while the conflict misses it removes, times the penalty, outweigh the slower hit — true for small caches and expensive misses, false for a large cache with a cheap penalty. That is why second- and third-level caches, whose hit time matters less and whose misses cost more, use 8 to 16 ways.

AMAT optimiser: every knob moves two terms

Two plots share the horizontal axis: on top the miss rate, below the AMAT it produces. With the knob "block size" you get the textbook's measured miss rates for four cache sizes, a 1-cycle hit and the penalty L + 2 · B/16; with "associativity" its miss rates for three sizes, a fixed penalty and the hit time × 1.36, 1.44, 1.52 for 2, 4 and 8 ways. Rings mark the lowest point of each curve, the yellow dot your setting; with the associativity knob, the orange band is what the misses add to the hit time. The insight: the metric is AMAT, and every knob moves two of its terms.

  1. Block size, 4 KB, L = 80 cycles. Before you move the slider: which block size gives the fewest misses, and which the lowest AMAT? Then step through the five sizes.
    What you should see

    Fewest misses at 64 bytes (7.00 %), lowest AMAT at 32 bytes (7.08 cycles against 7.16). From 32 to 64 bytes the miss rate falls by 0.24 points while every miss gets 4 cycles dearer, 84 → 88.

  2. Still at 4 KB, drag the latency L down to 0 and then up to 200. Predict where the AMAT optimum goes.
    What you should see

    At L = 0 only the transfer counts, every extra byte costs, and 16 bytes win. From L = 120 up the latency dominates, the penalty hardly grows with the block, and the AMAT optimum joins the miss-rate optimum at 64 bytes. Long latency favours big blocks; scarce bandwidth favours small ones.

  3. Back to L = 80, switch to 256 KB. Where are the two optima now?
    What you should see

    The miss rate is lowest at 128 and 256 bytes (0.49 %), AMAT at 64 bytes (1.45 cycles). A big cache has lines to spare, so the "fewer blocks" effect starts late and the miss-rate optimum moves right — the AMAT optimum stays at or left of it, as the stepper proved.

  4. Switch the knob to associativity, price on, penalty 25 cycles. Predict the best number of ways for 4 KB and for 512 KB. Then take 32 KB and raise the penalty to 200 cycles.
    What you should see

    4 KB: 4-way, 3.22 cycles against 3.45 direct-mapped. 512 KB: direct-mapped, 1.20 against 1.54 for 2-way — fact two of the hook. 32 KB is best direct-mapped at 25 cycles (2.05) but 4-way at 200 cycles (8.84 against 9.40): associativity pays where misses are expensive. Switch the price off and more ways never look worse — the miss-rate view.

Associativity on the cheap: a tray, a guess, a second address

Associativity removes conflict misses but slows every hit. Three tricks get most of the benefit without slowing the normal hit.

The victim cache: catch the block on its way out

Take an 8-line direct-mapped cache and a loop that alternates between blocks 5 and 13. Both map to line 5 (5 mod 8 = 13 mod 8 = 5), so each access throws out the block the next access needs — a ping-pong of conflict misses, with seven lines standing empty.

A victim cache (p. 328) is a small buffer of 4 to 16 entries, fully associative, placed between the cache and the next level. Every block evicted from the cache goes into it. On a miss the cache looks there: if the block is found, the two are swapped — the requested block returns to its line, the block it displaces takes its seat in the tray; if not, the block comes from the next level as usual (the flow on p. 329).

Two choices carry the idea. The tray is fully associative because its job is to hold blocks whose index collided: indexed the same way, they would collide again. And it is consulted only after a miss, so the hit path of the cache does not change — "No Change to Main Cache Hit Time" (p. 328).

Norman Jouppi, at DEC's research lab, proposed both the victim cache and the stream buffer of the next section in a single ISCA paper. When he received the Eckert-Mauchly Award in 2015, the citation noted that these ideas "have been widely adopted by industry with numerous machines containing victim caches and virtually all machines incorporating prefetching stream buffers".

Ping-pong on line 5, with and without a victim cache

An 8-line direct-mapped cache on the left (reads only, so D stays 0) and a 4-entry fully associative victim cache on the right; every block of the chosen pattern maps to line 5. The strip on top is the sequence of twelve accesses: orange means a trip to the next level, blue a block found in the victim cache. LRU 1 marks the tray entry that leaves first. Step through with the tray off, then on. The insight: a tiny buffer next to the cache removes the conflict misses without touching the hit path.

  1. Pattern 5, 13, 5, 13 …, victim cache off. Predict how many of the twelve accesses go to the next level.
    What you should see

    All twelve. The first two are compulsory (cold-start) misses; the other ten are conflict misses — each access throws out the block the next one needs, while seven lines stand empty.

  2. Turn the victim cache on and step again. What happens at access 3?
    What you should see

    Block 5 is not in line 5 (13 is) but it is in the tray: the two swap, 5 back into line 5 and 13 into the tray. From then on every access is a swap: 2 trips to the next level instead of 12.

  3. Choose the six-block pattern. Before stepping: does a 4-entry tray still help? And the three blocks of Example 10?
    What you should see

    Six blocks: no. Line 5 plus four entries are five seats for six blocks used in a circle, and the block needed next is always the one dropped longest ago — all twelve accesses go to the next level. Three blocks: 3 trips and 9 swaps. The tray rescues small conflict sets only, "very effective for small conflict sets" (p. 328).

Way prediction: knock on one door first

The notes' analogy (p. 332) is a hotel: floors are sets, rooms are ways. Without way prediction, staff knock on every door of the floor at once — quick, but noisy and costly. With it, the receptionist's notebook says where the guest was last time, and you knock there first.

The notebook is the way predictor table (WPT, p. 330): indexed by the set index, one entry per set, holding the way of the last hit (or of the last fill) — \(\lceil \log_2 n \rceil\) bits per entry. A 4-way cache with 1024 sets needs 1024 × 2 = 2048 bits, 256 bytes (p. 331). An access reads the predicted way only. If its tag matches, it is a correct-prediction hit in 1 cycle; if not, the other n − 1 ways are checked: found there, it is a misprediction-hit one cycle later; found nowhere, it is a miss. Example 11 of the notes (pp. 331–332), set 12 of a 4-way cache:

Example 11: three accesses to set 12. The entry changes whenever the guess was wrong.
AccessWPT(12) beforeBlock found inOutcomeTimeWPT(12) after
1way 3way 3correct-prediction hit1 cycleway 3
2way 3way 0misprediction-hit1 + 1 cyclesway 0
3way 0not in the setmiss: block loaded into way 2 (LRU)miss penaltyway 2
one hit, left to right in time read 2 ways compare tags mux data out hit time data waits for the compare

Before: the multiplexer waits

one hit, left to right in time WPT: way 1 read way 1 data out compare 1 tag confirms the hit mismatch: +1 cycle hit time mux set early

After: the WPT sets the multiplexer

Fig. 2. Where the time goes in a hit of a 2-way cache. Without prediction the data can leave only after the comparison has chosen the way. With way prediction the table chooses it before the comparison, the data leaves at once, as in a direct-mapped cache, and the comparison only confirms; a wrong guess costs a cycle to check the other way.

The pay-off: most hits are as fast and as cheap as in a direct-mapped cache, with the miss rate of the set-associative one. With prediction accuracy \(p\) and \(t_m\) extra cycles per wrong guess:

\[ \text{HT}_{\text{eff}} = \text{HT} + (1 - p)\, t_m \]

The notes quote 80–95 % accuracy (p. 332); at 90 % with one extra cycle, a 1-cycle hit becomes 1.1 cycles on average. What way prediction does not do is remove a miss: it changes how a hit is found, not what the cache holds. The notes' own p. 334 says so — it "reduces hit time", the family of lesson 28.

Pseudo-associativity: a second chance for a direct-mapped cache

A pseudo-associative (column-associative) cache stays direct-mapped but gives every block a second possible line, typically the index with its most significant bit flipped (p. 333). An access checks the primary line first — a hit there takes 1 cycle; then the secondary line — a hit there is a slow hit, one cycle more; then it is a miss. Take blocks 5 and 13 of the ping-pong above: both have line 5 as first choice and line 1 as second, so one can sit in each line instead of throwing the other out. Lines 1 and 5 together behave like one 2-way set, and most conflict misses disappear with little hardware (p. 334).

The price is a hit time with two values, which the pipeline must be able to absorb. Not in the notes, but standard: after a slow hit the two blocks are usually swapped, so that the block in use sits in the fast place next time. Memorise the notes' "Key Difference" box (p. 334): way prediction works on set-associative caches and cuts the hit time; pseudo-associativity works on direct-mapped caches and cuts conflict misses.

Fetching before the miss: stream buffers and prefetch instructions

Size, ways and trays leave one kind of miss untouched: the compulsory one. A block that has never been touched is nowhere above memory. The only way around it is to fetch the block before it is asked for, overlapping the fetch with useful work (p. 335).

In hardware: the stream buffer

Instructions run mostly in sequence, which makes instruction prefetching simple: on an instruction-cache miss, fetch the requested block into the cache and the next sequential block into an instruction stream buffer; when the next fetch hits in the buffer, that block moves into the cache and the one after it is requested (pp. 335–336). The notes' Example 13, the Alpha AXP 21064, is the textbook's case of exactly this.

Data prefetching works the same way for sequential or strided streams such as A[0], A[1], A[2] … (p. 336), with a D-stream buffer beside the cache (Fig. 3).

CPU L1 cache X+1 X+2 X+3 X+4 stream buffer (FIFO) next level X X+1 X+2 X+3 X+4 X+5 1 miss on X 2 X 3 4 no pollution
Fig. 3. A miss on block X (1) brings X into the cache (2); the prefetcher fills the stream buffer with X + 1 … X + 4 behind it (3); when the program asks for X + 1, it moves up from the buffer (4) — a fast hit instead of a miss — and X + 5 is requested. A wrong guess dies in the buffer and never evicts a useful block from the cache.

Why a separate buffer, and not straight into the cache? Because a guess can be wrong: a prefetched block that is never used would have evicted a useful one — cache pollution (p. 337). In the notes' picture, the waiter puts the next dish on a side tray; if nobody wants it, it goes back without touching your plate. What remains is the bandwidth spent on wrong guesses, and the need for a prefetch to arrive in time.

In software: the compiler issues the fetch

A hardware prefetcher sees only past addresses; the compiler sees the loop. It can insert prefetch instructions ahead of the use (p. 338): a register prefetch is a normal load done early (the notes cite HP PA-RISC), a cache prefetch an instruction that only brings the block into the cache (MIPS IV, PowerPC, SPARC v9). Example 15 prefetches eight elements ahead:

for (i = 0; i < N; i++) {
    __prefetch(&A[i+8]);  // 8 ahead
    sum += A[i];
}

The same loop in the papers' mnemonics, with 8-byte elements, so that eight elements ahead is 64 bytes. PREF stands for a cache-prefetch instruction (MIPS IV has one under that name; the papers have none):

LOOP: PREF  64(R1)       ; block of A[i+8]: cache only
      LD    F2, 0(R1)    ; F2 = A[i]
      FADD  F0, F0, F2   ; sum += A[i]
      ADDUI R1, R1, 8
      BNE   R1, R2, LOOP

Two conditions, not in the notes, make the idea work. The prefetch must be non-faulting: in the last iterations A[i+8] runs past the end of the array, and a prefetch to a bad address must quietly do nothing instead of raising an exception. And the cache must be non-blocking — able to keep serving the loop's loads while the prefetched block is on its way (lesson 26); a cache that stalls on a prefetch gains nothing.

The hard part is the prefetch distance (p. 339): too close, and the block arrives after the load that needed it — the penalty is still paid; too far, and it may be evicted before use. The usual rule: prefetch as many iterations ahead as one miss lasts,

\[ d = \left\lceil \frac{\text{miss latency}}{\text{cycles per iteration}} \right\rceil \]

— with a 100-cycle miss and a 12-cycle iteration, 9 iterations ahead. The costs (p. 339): issue slots for the extra instructions, less of a problem on a superscalar; bandwidth for blocks never used; and in Example 15 plain redundancy — with eight elements per 64-byte block, seven prefetches in eight ask for a block already requested, so a compiler unrolls the loop by eight and issues one per block (unrolling is lesson 15).

The compiler reorders the accesses

The last knob moves no hardware at all. The compiler rearranges the data or the loops so that the program's order of accesses fits the cache — related data together for spatial locality, reused data reused soon for temporal locality (p. 340). Four transformations.

Merging arrays

int val[SIZE];
int key[SIZE];
struct {
    int val;
    int key;
} merged_array[SIZE];

If val[i] and key[i] are always used together, two arrays keep them in two different blocks — a visit to some i can cost two misses — and two arrays of the same size can even map onto the same sets and evict each other. Merged into one array of structures, both fields sit in the same block (p. 340): one miss serves the pair, and the conflict between the arrays is gone.

Loop interchange

for (j = 0; j < 100; j++)
  for (i = 0; i < 5000; i++)
    x[i][j] = 2 * x[i][j];
for (i = 0; i < 5000; i++)
  for (j = 0; j < 100; j++)
    x[i][j] = 2 * x[i][j];

C stores arrays row by row: x[i][j] and x[i][j+1] are neighbours, while x[i][j] and x[i+1][j] are 100 words apart. The "before" loop walks down a column. With 4-byte words and 64-byte blocks, every access lands in a new block, and the loop comes back to that block only after visiting the other 4999 rows — 320 KB of blocks later. In a smaller cache the block is gone by then, and every one of the 500 000 elements misses. Interchanged, the loop walks along the rows and uses all 16 words of a block before moving on: 31 250 misses, sixteen times fewer — fact three of the hook. In Fortran, which stores arrays column by column, the good order is the other one; and the swap is legal only if no dependence between iterations forbids it.

Loop fusion

for (i = 0; i < N; i++)
  A[i] = B[i] + 1;
for (i = 0; i < N; i++)
  C[i] = A[i] * 2;
for (i = 0; i < N; i++) {
  A[i] = B[i] + 1;
  C[i] = A[i] * 2;
}

The first loop writes all of A; when A is larger than the cache, the second loop finds A[0] long evicted and misses on it again. Fused, each A[i] is used right after it was written, while still in the cache — temporal locality (p. 341). The cost is a longer loop body that needs more registers.

Blocking

In x = y × z with N × N matrices, every element of z is used N times — once for each row of y. Blocking computes on B × B sub-matrices, so that a tile of z is reused while it is still in the cache. The textbook's version, with the blocking factor B:

for (jj = 0; jj < N; jj += B)
  for (kk = 0; kk < N; kk += B)
    for (i = 0; i < N; i++)
      for (j = jj; j < min(jj+B, N); j++) {
        r = 0;
        for (k = kk; k < min(kk+B, N); k++)
          r = r + y[i][k] * z[k][j];
        x[i][j] = x[i][j] + r;
      }

The arithmetic is the same N³ multiply-adds; what changes is how many words travel from memory:

  1. Unblocked, in the worst case — the cache keeps neither a row of y nor a column of z between uses — each of the N² results reads a row of y and a column of z and writes one word of x:

    \[ N^2 \cdot 2N + N^2 = 2N^3 + N^2 \]
  2. Blocked: for one pair (jj, kk) the B × B tile of z is loaded once and then used by all N rows i. There are (N/B)² pairs:

    \[ \left(\frac{N}{B}\right)^{2} B^2 = N^2 \]
  3. For each pair and each row i, a strip of B words of y is loaded and reused for the B values of j; x likewise, read and written:

    \[ \left(\frac{N}{B}\right)^{2} N \cdot B = \frac{N^3}{B} \]
  4. Add z, y and x: about B times fewer words, as long as the tile and its two strips fit in the cache — about B² + 2B words:

    \[ \frac{2N^3}{B} + N^2 \]

Same accesses, different order: interchange and blocking

Tab "loop interchange": a shrunken x[8][32] of 4-byte words, stored row by row, 16 words per 64-byte block (the outlined groups); the cache holds 4 blocks, fully associative, LRU. A cell turns orange when its access missed, blue when it hit; the yellow cell is the current access. Tab "blocking": x = y × z with 16 × 16 matrices and a cache of 32 or 128 words (one word per block, LRU), simulated access by access; the darker a matrix, the more times each of its words was fetched; solid outline: the first tile of z, dashed: the strips of y and x it works with. The insight: the compiler cuts misses by changing the order of the accesses — the hardware is the same.

  1. Loop interchange, j outer (the notes' "Before"). A column touches 8 blocks before it returns to the first; the cache holds 4. Predict the misses after all 256 accesses, then press "Show all 256".
    What you should see

    256: every access misses. Each block is fetched 16 times, once for every column it holds.

  2. Switch to i outer ("After").
    What you should see

    16 misses: one per block, then 15 hits. Sixteen times fewer — the number of words in a block, as for x[5000][100].

  3. Blocking, cache of 32 words. Predict which tile size moves the fewest words, then try B = 1, 2, 4, 8 and 16 (B = 16 is the original loop).
    What you should see

    B = 4: 2304 words against 8448 for the original loop, exactly the estimate 2N³/B + N². At B = 8 the 64-word tile of z no longer fits in 32 words: z is fetched again for every row, and the count jumps to 5120.

  4. Now take the cache of 128 words.
    What you should see

    B = 8 fits and wins: 1280 words, against 4608 for the original loop with this cache. A bigger cache allows a bigger tile; the best tile is the largest one that fits.

The eight knobs side by side

The eight knobs of this lesson: what each removes, what it lowers, what it costs. Lesson 28 sets them next to the miss-penalty and hit-time techniques in one scorecard.
TechniqueMisses it removesAMAT term it lowersWhat it costs
Larger cachecapacitymiss ratehit time, area, power
Larger blockcompulsorymiss rate, up to a pointmiss penalty; fewer blocks bring conflict and capacity misses back
Higher associativityconflictmiss ratehit time (multiplexer after the compare), power
Victim cacheconflictmiss rate (or miss penalty, seen from the cache alone)a few entries, comparators, swap logic
Way predictionnonehit time and powera small table; +1 cycle on a wrong guess
Pseudo-associativityconflictmiss ratea second, slower hit time
Prefetching, hardware or softwarecompulsory (and some capacity)miss ratebandwidth; extra instructions (software); needs a non-blocking cache
Compiler: merging, interchange, fusion, blockingcapacity, conflictmiss ratecompiler work, code size; no hardware

Back to the three facts

Fact one: 64-byte blocks miss less, but each miss costs 4 cycles more — 7.00 % × 88 = 6.16 cycles of misses per access against 7.24 % × 84 = 6.08. Fact two: the 2-way 512 KB cache saves 0.1 % × 25 = 0.025 cycles of misses per access and adds 0.36 cycles to every hit. Fact three: same cache, same 500 000 elements, but each 64-byte block now serves 16 of them instead of one.

The metric is AMAT, not the miss rate: the plain hardware knobs cut misses but push up another term, and the compiler cuts misses by changing only the order of the accesses.

Exam-style questions

(In the form of the June 2026 Question 1.) Explain the main concepts and benefits of each one of the following cache optimization techniques: 1. Larger block size; 2. Higher associativity; 3. Victim cache; 4. Way prediction; 5. Pseudo-associativity; 6. Hardware prefetching with stream buffers; 7. Software (compiler-controlled) prefetching; 8. Loop interchange and loop blocking.

Show model answer

1. Larger block size: a miss brings in the neighbouring words too, exploiting spatial locality, so compulsory misses decrease. For a fixed cache size there are fewer blocks, so beyond an optimum conflict and capacity misses grow (U-shaped miss rate), and the miss penalty grows with the block (latency + transfer). The AMAT-optimal block is at or below the miss-rate-optimal one; common sizes are 16–64 bytes.

2. Higher associativity: a block can be placed in any of the n ways of its set, so blocks with the same index can coexist: conflict misses decrease, compulsory and capacity misses do not change. Rules of thumb: a direct-mapped cache of size N misses about as often as a 2-way cache of size N/2; 8-way is practically fully associative. Cost: the way-select multiplexer must wait for the n tag comparisons, so the hit time (clock cycle) and the power grow; for large caches AMAT can increase.

3. Victim cache: a small (4–16 entries) fully associative buffer between the cache and the next level, holding the blocks recently evicted. On a miss it is checked; on a hit there the block is swapped back into the cache. It removes many conflict misses of a direct-mapped cache (a few blocks that keep evicting each other) without changing the hit time of the main cache; low cost. Seen from the main cache, it makes those misses cheap (miss-penalty view).

4. Way prediction: a small table indexed by the set index stores, for each set, the way that hit last (log2 n bits). Only the predicted way is read and its data is sent out at once, as in a direct-mapped cache; one tag comparison confirms it. On a mismatch the other ways are checked (+1–2 cycles); the table is updated on every wrong prediction, misses included. Benefit: hit time and energy close to direct-mapped with the miss rate of a set-associative cache (accuracy 80–95 %). It does not reduce the miss rate.

5. Pseudo-associativity: a direct-mapped cache where each block has a second possible line (e.g. the index with its MSB flipped). Check the primary line (1-cycle hit), then the secondary (slow hit, +1 cycle), then miss. Two blocks with the same index can now both stay, one in each line (the pair of lines acts as one 2-way set), so it removes many conflict misses with little hardware; cost: two different hit times, harder to pipeline; blocks are swapped so that the most recently used one is in the primary line.

6. Hardware prefetching: on a miss on block X the hardware fetches X into the cache and X+1, X+2, … into a stream buffer next to it (instruction stream buffer for sequential code, data stream buffers for sequential or strided accesses). If the next access finds the block in the buffer, it is moved into the cache and the following one is prefetched. It removes compulsory (and some capacity) misses by overlapping memory accesses with execution; the separate buffer avoids polluting the cache with wrong guesses. Cost: memory bandwidth, useless prefetches.

7. Software prefetching: the compiler inserts prefetch instructions ahead of the use (e.g. prefetch A[i+8] while using A[i]), at a distance of about miss latency / cycles per iteration. Cache prefetch (into the cache, not a register) is the common form; the prefetch must be non-faulting and the cache non-blocking, so that execution continues while the block arrives. Benefits: removes compulsory/capacity misses of predictable patterns the hardware may not detect, tunable by profiling. Costs: extra instructions (issue slots), bandwidth; too close is useless, too far may be evicted.

8. Loop interchange swaps nested loops so that the inner loop walks memory sequentially (row-major C arrays: inner loop on the last index), so each block fetched serves all its words before eviction (e.g. 16 times fewer misses with 4-byte words and 64-byte blocks). Loop blocking (tiling) splits a computation that reuses data, such as matrix multiplication, into B × B tiles that fit in the cache, so that each tile is reused before eviction: words moved go from 2N³ + N² to 2N³/B + N². Both reduce capacity (and conflict) misses at no hardware cost; they must respect the data dependences.

A complete sub-point has three parts: the mechanism in one or two sentences, the kind of miss (or the term of AMAT) it attacks, and its cost. Two classic traps: crediting way prediction with fewer misses, and saying that larger blocks reduce capacity misses. Short of time, write the AMAT term and the miss category first — they carry the answer.

A 64 KB cache has a hit time of 1 clock cycle. Its measured miss rate is 2.04 % with 16-byte blocks, 1.35 % with 32-byte blocks, 1.06 % with 64-byte blocks, 1.02 % with 128-byte blocks and 1.15 % with 256-byte blocks. On a miss, main memory delivers the first 16 bytes after 80 clock cycles and 16 more bytes every 2 clock cycles. Which block size (in bytes) minimizes the average memory access time?

Show model answer

Miss penalty = 80 + 2 × (B/16) clock cycles; AMAT = 1 + Miss rate × Miss penalty.

Block sizeMiss rateMiss penaltyAMAT
16 B2.04 %821 + 0.0204 × 82 = 2.67
32 B1.35 %841 + 0.0135 × 84 = 2.13
64 B1.06 %881 + 0.0106 × 88 = 1.93
128 B1.02 %961 + 0.0102 × 96 = 1.98
256 B1.15 %1121 + 0.0115 × 112 = 2.29

The minimum AMAT is 1.93 clock cycles with 64-byte blocks, although the lowest miss rate is at 128 bytes: from 64 to 128 bytes the miss rate drops only from 1.06 % to 1.02 % while the miss penalty grows from 88 to 96 cycles.

The tempting wrong answer is 128, the miss-rate optimum. Write the penalty formula first and the table second: a slip then stays in one row, where it is easy to spot.

Let's consider a 32-block main memory with a direct-mapped 8-block cache based on a write allocate with write-back protocol. The addresses are expressed as: Memory Addresses: [0, 1, 2, … 31]10; Cache Addresses: [a, b, c, d, e, f, g, h] and Cache Tags are expressed as binary numbers. At cold start the cache is empty, then there is the following sequence of memory accesses: Read [03]10, Read [11]10, Write [03]10, Read [11]10, Write [05]10, Read [03]10, Read [13]10, Read [05]10.

(a) Please complete the table (Type of memory access, Memory Address, Hit/Miss Type, Cache Tag, Set, Cache Address, Dirty bit, Write in memory). How much is the miss rate? (b) Repeat with a 2-way set associative 8-block cache with LRU. (c) Which misses of (a) disappear in (b), and why? (d) How many accesses of (a) must still fetch the block from main memory if a 2-entry fully associative victim cache is added to the direct-mapped cache?

Show model answer

(a) Direct-mapped: 8 sets, Set = address mod 8, Tag = address div 8 (2 bits); set k is cache address a + k (set 3 = d, set 5 = f).

#Type of memory accessMemory AddressHit/Miss TypeCache TagSetCache AddressDirty bitWrite in memory
1Read[03]10Cold-start Miss[00]2[3]10d0No
2Read[11]10Cold-start Miss[01]2[3]10d0No
3Write[03]10Conflict Miss[00]2[3]10d1No
4Read[11]10Conflict Miss[01]2[3]10d0Yes (write-back of block 3)
5Write[05]10Cold-start Miss[00]2[5]10f1No
6Read[03]10Conflict Miss[00]2[3]10d0No
7Read[13]10Cold-start Miss[01]2[5]10f0Yes (write-back of block 5)
8Read[05]10Conflict Miss[00]2[5]10f0No

Miss rate = 8/8 = 100 %.

(b) 2-way: 4 sets, Set = address mod 4, Tag = address div 4 (3 bits); set 0 = a, b; set 1 = c, d; set 2 = e, f; set 3 = g, h.

#Type of memory accessMemory AddressHit/Miss TypeCache TagSetCache AddressDirty bitWrite in memory
1Read[03]10Cold-start Miss[000]2[3]10g0No
2Read[11]10Cold-start Miss[010]2[3]10h0No
3Write[03]10Hit[000]2[3]10g1No
4Read[11]10Hit[010]2[3]10h0No
5Write[05]10Cold-start Miss[001]2[1]10c1No
6Read[03]10Hit[000]2[3]10g1No
7Read[13]10Cold-start Miss[011]2[1]10d0No
8Read[05]10Hit[001]2[1]10c1No

Miss rate = 4/8 = 50 %.

(c) The four conflict misses of (a), rows 3, 4, 6 and 8, become hits. Blocks 3 and 11 share a set in both caches (and so do 5 and 13), but the 2-way set holds both, while the direct-mapped line holds one at a time. The four cold-start misses remain: associativity does not remove compulsory misses. The two write-backs of (a) disappear too, because no dirty block is evicted.

(d) 4: only the cold-start misses (accesses 1, 2, 5, 7). Each conflict miss of (a) finds its block in the victim cache, where the previous access to the same line put it (access 6 finds block 3, evicted by access 4), and swaps it back into the line.

Label the misses as the July 2024 paper does ("Cold-start Miss" for a first reference) and say "Conflict Miss" when a block comes back after being evicted although the cache had free room: in a fully associative cache of 8 blocks these four accesses would hit. Dirty bits and write-backs are where most slips happen: memory is written only when a dirty block is evicted, never on the write itself.

A 32 KB 8-way set associative cache with 64-byte blocks uses way prediction. How many bits does the way predictor table need?

Show model answer

Number of sets = 32 768 / (64 × 8) = 64. Each entry names one of 8 ways: log2 8 = 3 bits. Table size = 64 × 3 = 192 bits (24 bytes).

The table has one entry per set, not per block: the tempting wrong answers are 512 × 3 = 1536 (one entry per block) and 64 × 8 = 512 (one bit per way).

(MULTIPLE ANSWERS) Which of the following techniques reduce conflict misses?

  • Answer 1: Increasing the associativity of the cache Blocks with the same index get several places in the set.
  • Answer 2: Adding a small fully associative victim cache The block evicted by a collision waits in the victim cache and is swapped back.
  • Answer 3: Pseudo-associativity A direct-mapped cache gets a second line for each index, so two colliding blocks can both stay.
  • Answer 4: Increasing the block size for the same cache size It reduces compulsory misses, and with fewer blocks in the cache it increases conflict misses.
  • Answer 5: Way prediction It never changes what the cache holds; it reduces the hit time and the power of hits.
Show model answer

Answers 1, 2 and 3.

The quizzes of the papers build their wrong options from neighbouring techniques; the January 2026 paper, solved in the notes' Exams chapter, asked the same question about the miss penalty (lesson 24). Sort each technique by the term of AMAT and the C it attacks — the table at the end of section 6.

(SINGLE ANSWER) A 4-way set associative L1 cache adds a way predictor. Which statement is correct?

  • Answer 1: It reduces the miss rate, because the most likely way is checked first The order of the checks does not change what is in the cache: the same accesses miss.
  • Answer 2: It reduces the hit time and the energy of most hits, while the miss rate is unchanged Right: a correctly predicted hit reads one way and does not wait for the tag comparison; a wrong guess costs an extra cycle.
  • Answer 3: It removes the tag comparison from the access The predicted way's tag is still compared — to confirm the hit, in parallel with sending the data.
  • Answer 4: The predictor is updated only on hits After a miss the entry points to the way that received the new block (Example 11, access 3).
Show model answer

Answer 2.

Answer 4 is the sentence printed on p. 331 of the notes, which their own Example 11 contradicts one page later: after a miss the entry is rewritten too.

(True/False, motivate your answer.) "Loop interchange, loop fusion and loop blocking reduce the compulsory misses of a program."

  • True They reorder the accesses but touch the same blocks, so every block still has its first, compulsory miss.
  • False Right: they remove repeat misses to blocks evicted and fetched again — capacity and conflict misses.
Show model answer

False. A compulsory miss is the first access to a block. These transformations change only the order of the accesses (and the loop structure), not the set of blocks the program touches, so the number of first accesses is unchanged. They reduce capacity (and conflict) misses: a block is used completely (interchange) or reused (fusion, blocking) before it is evicted.

The notes' table on p. 341 says the opposite. In the papers a motivation earns its own extra point: it must say what a compulsory miss is and why reordering the accesses cannot change their number.

Check yourself

A 4 KB cache goes from 64-byte to 128-byte blocks and its miss rate rises from 7.00 % to 7.78 %. Which misses grew?

  • Conflict and capacity misses: the cache now has 32 lines instead of 64 Right: fewer, larger blocks — blocks still in use get thrown out to make room.
  • Compulsory misses Larger blocks reduce first touches: one miss now brings in twice as many neighbours.
  • None: the miss rate rose because the miss penalty rose from 88 to 96 cycles That is the sentence of p. 325 the red pen corrects: the penalty raises AMAT, not the number of misses.

Which of these lower the miss rate without adding anything to the hit time of the L1? Select all that apply.

  • A 4-entry victim cache It is consulted only after a miss; a hit in the cache is as fast as before.
  • Loop interchange Same cache, same hit time; the accesses are just in a better order.
  • Prefetching into a stream buffer The buffer sits beside the cache, and only the accesses that would have missed use it.
  • Doubling the L1 A larger array has a longer access path: the hit time grows.
  • Going from 2-way to 8-way The multiplexer waits for more comparisons, and the hit time grows (× 1.36 → × 1.52 in the textbook's example).
  • Way prediction It does not lower the miss rate at all; it lowers the hit time.

The three right answers are the tricks of sections 4–6; the three wrong ones are the plain knobs, or a hit-time technique.

In a 4-way cache, WPT(7) = way 1. An access to set 7 misses, and the block is loaded into way 3, the LRU way. What does WPT(7) hold afterwards?

  • Way 3 Right: the guess was wrong, and the block just loaded is the most likely next hit.
  • Way 1, because the table changes only on hits That is the claim of p. 331 corrected by the red pen; the notes' own Example 11 updates on a miss.
  • Way 0, the next way in order The table records where the block is, not a fixed rotation.

A loop iteration takes 15 cycles and a miss costs 120 cycles. How far ahead should a software prefetch be issued?

  • 1 iteration ahead The block would arrive 105 cycles after the load that needs it: most of the penalty is still paid.
  • 8 iterations ahead Right: 120 / 15 = 8 iterations is the time a miss lasts, so the block arrives just in time.
  • 120 iterations ahead Far too early: 120 iterations later the block may have been evicted by the data used in between.

Fortran stores X(I,J) column by column: X(I,J) and X(I+1,J) are neighbours. Which loop order walks memory sequentially?

  • Outer loop on J, inner loop on I Right: the inner loop must run along the storage order, here down a column.
  • Outer loop on I, inner loop on J That is the good order in C; in Fortran it jumps a whole column at every step.
  • Either: the cache does not care about the order The order decides whether the words of a fetched block are used before it is evicted — a sixteen-fold difference in section 6.

A 512 × 512 matrix multiplication is blocked with B = 32, and the tile fits in the cache. By about how much do the words moved from memory shrink?

  • Not at all: the multiplications are the same The arithmetic is the same; the traffic is not — that is the whole point of blocking.
  • About 31 times Right: (2N³ + N²) / (2N³/B + N²) = 268 697 600 / 8 650 752 ≈ 31, close to B.
  • About 1024 times (B²) z is fetched once instead of N times, but y and x are still fetched N/B times each: the gain is about B.

Key take-aways

What to watch and read