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".
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:
- 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.
Before: 16-byte blocks — 4 misses in 16 accesses
After: 64-byte blocks — 1 miss in 16 accesses
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.
- \(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:
| Block size | Miss rate | Miss penalty (cycles) | AMAT = 1 + MR × MP |
|---|---|---|---|
| 16 B | 8.57 % | 82 | 8.03 |
| 32 B | 7.24 % | 84 | 7.08 |
| 64 B | 7.00 % | 88 | 7.16 |
| 128 B | 7.78 % | 96 | 8.47 |
| 256 B | 9.51 % | 112 | 11.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:
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} \]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^*) \]Its penalty is higher, because more bytes must travel:
\[ \text{MP}(B) \gt \text{MP}(B^*) \]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^*) \]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.
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).
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:
| Access | WPT(12) before | Block found in | Outcome | Time | WPT(12) after |
|---|---|---|---|---|---|
| 1 | way 3 | way 3 | correct-prediction hit | 1 cycle | way 3 |
| 2 | way 3 | way 0 | misprediction-hit | 1 + 1 cycles | way 0 |
| 3 | way 0 | not in the set | miss: block loaded into way 2 (LRU) | miss penalty | way 2 |
Before: the multiplexer waits
After: the WPT sets the multiplexer
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:
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).
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,
— 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:
Unblocked, in the worst case — the cache keeps neither a row of
\[ N^2 \cdot 2N + N^2 = 2N^3 + N^2 \]ynor a column ofzbetween uses — each of the N² results reads a row ofyand a column ofzand writes one word ofx:Blocked: for one pair (jj, kk) the B × B tile of
\[ \left(\frac{N}{B}\right)^{2} B^2 = N^2 \]zis loaded once and then used by all N rows i. There are (N/B)² pairs:For each pair and each row i, a strip of B words of
\[ \left(\frac{N}{B}\right)^{2} N \cdot B = \frac{N^3}{B} \]yis loaded and reused for the B values of j;xlikewise, read and written:Add
\[ \frac{2N^3}{B} + N^2 \]z,yandx: about B times fewer words, as long as the tile and its two strips fit in the cache — about B² + 2B words:
The eight knobs side by side
| Technique | Misses it removes | AMAT term it lowers | What it costs |
|---|---|---|---|
| Larger cache | capacity | miss rate | hit time, area, power |
| Larger block | compulsory | miss rate, up to a point | miss penalty; fewer blocks bring conflict and capacity misses back |
| Higher associativity | conflict | miss rate | hit time (multiplexer after the compare), power |
| Victim cache | conflict | miss rate (or miss penalty, seen from the cache alone) | a few entries, comparators, swap logic |
| Way prediction | none | hit time and power | a small table; +1 cycle on a wrong guess |
| Pseudo-associativity | conflict | miss rate | a second, slower hit time |
| Prefetching, hardware or software | compulsory (and some capacity) | miss rate | bandwidth; extra instructions (software); needs a non-blocking cache |
| Compiler: merging, interchange, fusion, blocking | capacity, conflict | miss rate | compiler 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 size | Miss rate | Miss penalty | AMAT |
|---|---|---|---|
| 16 B | 2.04 % | 82 | 1 + 0.0204 × 82 = 2.67 |
| 32 B | 1.35 % | 84 | 1 + 0.0135 × 84 = 2.13 |
| 64 B | 1.06 % | 88 | 1 + 0.0106 × 88 = 1.93 |
| 128 B | 1.02 % | 96 | 1 + 0.0102 × 96 = 1.98 |
| 256 B | 1.15 % | 112 | 1 + 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 access | Memory Address | Hit/Miss Type | Cache Tag | Set | Cache Address | Dirty bit | Write in memory |
|---|---|---|---|---|---|---|---|---|
| 1 | Read | [03]10 | Cold-start Miss | [00]2 | [3]10 | d | 0 | No |
| 2 | Read | [11]10 | Cold-start Miss | [01]2 | [3]10 | d | 0 | No |
| 3 | Write | [03]10 | Conflict Miss | [00]2 | [3]10 | d | 1 | No |
| 4 | Read | [11]10 | Conflict Miss | [01]2 | [3]10 | d | 0 | Yes (write-back of block 3) |
| 5 | Write | [05]10 | Cold-start Miss | [00]2 | [5]10 | f | 1 | No |
| 6 | Read | [03]10 | Conflict Miss | [00]2 | [3]10 | d | 0 | No |
| 7 | Read | [13]10 | Cold-start Miss | [01]2 | [5]10 | f | 0 | Yes (write-back of block 5) |
| 8 | Read | [05]10 | Conflict Miss | [00]2 | [5]10 | f | 0 | No |
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 access | Memory Address | Hit/Miss Type | Cache Tag | Set | Cache Address | Dirty bit | Write in memory |
|---|---|---|---|---|---|---|---|---|
| 1 | Read | [03]10 | Cold-start Miss | [000]2 | [3]10 | g | 0 | No |
| 2 | Read | [11]10 | Cold-start Miss | [010]2 | [3]10 | h | 0 | No |
| 3 | Write | [03]10 | Hit | [000]2 | [3]10 | g | 1 | No |
| 4 | Read | [11]10 | Hit | [010]2 | [3]10 | h | 0 | No |
| 5 | Write | [05]10 | Cold-start Miss | [001]2 | [1]10 | c | 1 | No |
| 6 | Read | [03]10 | Hit | [000]2 | [3]10 | g | 1 | No |
| 7 | Read | [13]10 | Cold-start Miss | [011]2 | [1]10 | d | 0 | No |
| 8 | Read | [05]10 | Hit | [001]2 | [1]10 | c | 1 | No |
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?
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?
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."
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?
Which of these lower the miss rate without adding anything to the hit time of the L1? Select all that apply.
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?
A loop iteration takes 15 cycles and a miss costs 120 cycles. How far ahead should a software prefetch be issued?
Fortran stores X(I,J) column by column: X(I,J) and X(I+1,J) are neighbours. Which loop order walks memory sequentially?
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?
Key take-aways
- The metric is AMAT, not the miss rate: the plain hardware knobs that cut misses push up another cost — the hit time (size, ways), the penalty (block size) or the memory bandwidth (prefetching).
- A bigger cache removes capacity misses with diminishing returns and a slower hit, which is why the L1 stays small; a bigger block removes compulsory misses until the scarcity of lines brings conflicts back (the U-curve), and the AMAT-best block is never larger than the miss-rate-best one, because the penalty grows with the block.
- Associativity removes conflict misses only (2:1 rule, 8-way ≈ fully associative); its multiplexer waits for the tag comparison on every hit, so a large cache can be faster direct-mapped.
- A victim cache (a few fully associative entries, swap on a hit) and pseudo-associativity (a second line, so two lines act as one 2-way set) remove conflict misses without slowing the normal hit; way prediction keeps the miss rate and cuts the hit time and power, and its table is rewritten on every wrong guess, misses included.
- Prefetching attacks compulsory misses by fetching early: stream buffers beside the cache, so wrong guesses do not pollute it; software prefetches that are non-faulting, need a non-blocking cache and run about one miss latency ahead.
- Merging arrays, interchange, fusion and blocking change only the layout and order of the accesses: they cut capacity and conflict misses for free, never compulsory ones, and blocking pays only when data is reused — 2N³ + N² → 2N³/B + N² words for matrix multiplication.
What to watch and read
-
Venus RISC-V simulator, Cache tab
UC Berkeley CS 61C
After section 2: write a loop over an array, run it with a stride of one word and then with a stride of one block, and watch the hit rate collapse; then keep the size and switch from direct-mapped to 2-way to see conflict misses vanish. Its cache is write-through, which does not matter for these experiments.
-
Gallery of Processor Cache Effects
Igor Ostrovsky
Seven tiny experiments on a real machine. Example 2 shows the block at work (touching every 16th integer costs as much as touching all of them), Example 5 shows associativity at work (strides that are multiples of a power of two pile into one set).
-
Improving Direct-Mapped Cache Performance by the Addition of a Small Fully-Associative Cache and Prefetch Buffers
Norman P. Jouppi, ISCA 199010 pages
The paper behind sections 4 and 5 of this lesson: victim caches and stream buffers, with its measurements on direct-mapped caches. Read the abstract and the figures first.
-
What Every Programmer Should Know About Memory
Ulrich Drepper, Red Hat, 2007section 6
Section 6, "What programmers can do", runs this lesson's compiler and prefetching ideas on a real processor: the matrix multiplication rewritten for the cache, with transposition and sub-matrices, and hardware and software prefetching with measured speed-ups.