5.4.1 · D4Memory Hierarchy & Caches

Exercises — Principle of locality (temporal - spatial)

3,463 words16 min readBack to topic

This page is a self-test ladder. Each rung is harder than the last: L1 you just recognise the idea, by L5 you design around it. Read the problem, try it yourself, then open the collapsible solution.

Everything here builds on the parent locality note. If a term feels shaky, the Cache organization fundamentals and Cache mapping strategies notes hold the prerequisites; Working set model and Loop blocking and tiling extend the last few exercises.

Before we start, some words we will lean on constantly:

Figure — Principle of locality (temporal - spatial)

Figure s01 — your mental model for the whole page: memory is a long tape of bytes (top row, addresses increasing rightward); one block is the orange fixed-width window of contiguous bytes that gets fetched together on a miss; the teal shelf at the bottom is the cache, holding only such windows at a time. Keep this picture in mind — every exercise is really "which window is on the shelf right now?"


Level 1 — Recognition

Goal: can you name which locality is at play?

Exercise 1.1

For each access pattern, say temporal, spatial, or both:

  • (a) for(i=0;i<n;i++) s += a[i]; — the variable s
  • (b) the same loop — the array elements a[i]
  • (c) while(1){ handle(); } — the instructions of handle
  • (d) reading p->next->next->next down a linked list scattered across memory
Recall Solution 1.1

(a) s sits at one address, touched every iteration → temporal. (b) a[0], a[1], … are different but adjacent addresses → spatial. (There's mild temporal reuse too, but the dominant effect is spatial.) (c) The instruction addresses of handle repeat each loop → temporal. (Within one call they also run in sequence → spatial. So really both, but the loop makes temporal dominate.) (d) Each next is a new, far-away address with no neighbour reuse and no repeat → neither. This is the classic locality-killer: pointer chasing.

Exercise 1.2

A cache holds blocks. Address is accessed, then 5 distinct other blocks are accessed, then is accessed again. Hit or miss on the second ?

Recall Solution 1.2

The reuse distance (defined above) is (distinct blocks touched between the two references to ). Rule for our fully-associative LRU model: hit iff . Here , so is still on the shelfhit. (In a set-associative cache this could still miss if those 5 blocks all collide with 's set — a conflict miss — but with full associativity we are safe.)


Level 2 — Application

Goal: plug numbers into the reuse-distance and stride machinery.

Exercise 2.1

A 64-byte block. int is 4 bytes. You loop for(i=0;i<64;i++) sum += a[i]; with stride 1. How many misses, how many hits, and what hit rate (spatial only)?

Recall Solution 2.1

Using the word size bytes defined above, one block holds ints. Accessing 64 ints in order:

  • Access to a[0] misses, pulls in a[0..15] → next 15 are hits.
  • Pattern repeats every 16 ints. This matches the parent's steady-state formula, which we can now state cleanly with the symbols defined here: for stride-1 access the miss rate is (one miss per block of elements), so the hit rate is

Exercise 2.2

Same block, same array, but now for(i=0;i<64;i+=8)stride 8 ints = 32 bytes. How many misses out of the 8 accessed elements?

Recall Solution 2.2

You access a[0], a[8], a[16], …, a[56] — that's 8 elements. Stride in bytes . Block is 64 bytes, so two of your accessed elements can fit in one block:

  • Block containing a[0..15] holds a[0] and a[8] → 1 miss + 1 hit.
  • Block containing a[16..31] holds a[16] and a[24] → 1 miss + 1 hit.
  • … four blocks total. Doubling stride roughly halved our spatial benefit.

Exercise 2.3

Stride grows to 16 ints = 64 bytes, exactly one block. Accessing 8 elements. Misses?

Recall Solution 2.3

Now each accessed element lands in a fresh block (stride = block size). No two share a block. Spatial locality is completely dead once the stride reaches the block size — every fetch drags in 60 useless bytes.

Figure — Principle of locality (temporal - spatial)

Figure s02 — the pedagogical point of Exercises 2.1–2.3 in one image: a single 64-byte block splits into 16 int-slots. The teal slots are the ones stride-1 access actually touches (many hits per block); the plum slots show stride-8 touching only 2 of the 16, so the other 14 bytes are fetched-but-wasted. The figure makes the L2 trap visible — hits come from slots you touch, not from slots that exist.


Level 3 — Analysis

Goal: reason about a whole algorithm's locality, including the bad cases.

Exercise 3.1 — Row vs column traversal

Row-major matrix, , int = 4 bytes, block = 64 bytes (16 ints).

  • (a) Summing row by row: for i { for j { s += M[i][j]; } }. Miss rate?
  • (b) Summing column by column: for j { for i { s += M[i][j]; } }. Miss rate?
Recall Solution 3.1

Row-major means M[i][j] sits at address . Along a row ( varies) addresses are contiguous; down a column ( varies) they jump by bytes.

(a) Row by row — stride 1. 16 ints per block → 1 miss per 16 accesses.

(b) Column by column — stride 4096 bytes. Compare stride to block size directly: stride , block , and . Because the stride is 64 whole blocks long, consecutive accesses never share a block — each M[i][j] for successive lands in a different, freshly-fetched block, so every access is a compulsory miss. Could an earlier column's block still be resident when we come back? No: between two accesses to the same block (one full column apart), we touch other blocks, so the reuse distance is . Any cache with fewer than blocks (which, at 64 B each, means smaller than 64 KB) has → evicted before reuse. For the realistic caches assumed on this page, therefore:

Same arithmetic, same data — a 16× difference in miss rate purely from traversal order.

Figure — Principle of locality (temporal - spatial)

Figure s03 — Exercise 3.1 drawn on a small row-major grid. The teal arrow sweeps a row: consecutive addresses, so 16 elements ride in on one block (6.25% miss). The plum arrow walks a column: each step jumps elements to a brand-new block (100% miss). The two arrows carry identical arithmetic — the figure shows that only the traversal order differs, which is the whole lesson of L3.

Exercise 3.2 — Reuse distance decides it

A cache holds blocks. Two loops each re-access the same address after touching, respectively, and distinct blocks in between. Which loop's reuse is a hit?

Recall Solution 3.2

Hit iff (fully-associative LRU).

  • Loop 1: hit (the block survived).
  • Loop 2: miss (evicted before reuse). The temporal locality of loop 1 is captured by this cache; loop 2's is not. Enlarging the cache to blocks would rescue loop 2 — this is exactly why bigger caches capture longer reuse distances.

Level 4 — Synthesis

Goal: combine locality with a cost model and make a design decision.

Exercise 4.1 — Is a bigger block worth it?

Fixed cache size 32 KB, fully-associative LRU.

  • Option A: block = 32 B → blocks.
  • Option B: block = 256 B → blocks.

A workload streams sequentially through a 4 KB region once (pure spatial, stride 1), then randomly re-accesses 500 distinct 32-byte units with strong temporal reuse. For the temporal phase, count how many 32-byte units each configuration can actually hold, then decide which block size wins.

Recall Solution 4.1

Spatial part (streaming 4 KB once):

  • A: misses.
  • B: misses. Bigger block wins the streaming phase (fewer misses, better spatial locality).

Temporal part — do the capacity arithmetic, don't assert it. The cache is 32 KB = 32768 bytes either way. Convert its total capacity into 32-byte units: Total bytes is fixed, so both configurations physically store 1024 of our 32-byte working-set units. Since the working set is only 500 units, both A and B can retain all 500neither thrashes on the temporal phase. (Option B holds lines units each units; Option A holds lines unit units. Same total.)

So the temporal phase is a tie on this idealised fully-associative cache. The decision therefore turns on the spatial phase, where Option B had far fewer misses ( vs ).

Verdict: on this idealised model, Option B (256-byte blocks) wins — it matches A's temporal capacity yet crushes the streaming misses.

The real-world caveat (why "bigger is always better" is still false). This tie only holds because we assumed full associativity and a working set of exactly 500 units that each map to whatever free slot. Two things break Option B in practice:

  1. If the 500 units are scattered (not 8-consecutive), a 256-byte line drags in up to 7 useless units per fetch — you may need far more than 500 lines' worth of bytes to cover 500 scattered units, and then B does run short of lines.
  2. Real caches are set-associative; larger lines mean fewer sets, so more conflict misses. The clean tie above is the best case for big blocks; the parent Cache mapping strategies and the L4 trap below show where it collapses.

Exercise 4.2 — Derive the crossover for scattered working-set units

Now suppose the 500 working-set units are scattered — no two share a block. Then each unit needs its own line, and a config with line size (the same quantity as the block size — we just rename it because here it is the variable we are choosing) holds only lines. What is the largest line size that still holds all 500 scattered units?

Recall Solution 4.2

Need (number of lines) (number of scattered units): So when units are scattered, a 64-byte line is the sweet spot: as spatially generous as possible while still giving 500 distinct lines. Push to 256-byte lines and you drop to lines → now Option B does thrash. This is precisely the "bigger blocks trade temporal capacity for spatial reach" trade-off — but only once locality within a line is poor.


Level 5 — Mastery

Goal: reason at the level of designing an access pattern from scratch.

(From here on, plain denotes the matrix in a multiply. The cache block size stays , exactly as defined at the top — no collision.)

Exercise 5.1 — Blocking the matrix multiply

The naive i,j,k multiply reads down a column (stride , miss every access). We tile the loops so each inner region works on a sub-block that fits in cache. If cache holds ints and we must simultaneously hold sub-blocks of , , and (three tiles), what is the largest tile size ? Evaluate for ints.

Recall Solution 5.1

Three tiles of ints each must coexist: For : , so pick (or a friendly power-of-two-ish in practice for alignment).

Why it works: once a tile of matrix is loaded, every element is reused times before eviction — we converted the terrible stride- column reads into temporal locality on a small resident tile. This is the payoff of Loop blocking and tiling.

Exercise 5.2 — Reuse count after blocking

In the tiled multiply above, how many times is each loaded element of a -tile reused before it leaves cache, and what does that do to matrix 's effective miss rate compared to the naive ?

Recall Solution 5.2

Each element of a -tile participates in updating a whole column-strip of the -tile → it is used times per tile pass. So one miss now serves useful accesses: For : miss rate , versus naive — a ~52× reduction in misses on . That is locality engineered by hand.

Exercise 5.3 — Working set and virtual memory

A program's working set (distinct pages touched in a time window) is 200 pages. Physical memory gives it 150 page frames. Predict the behaviour and connect it to locality.

Recall Solution 5.3

Working set frames . The program cannot keep its actively-used pages resident → each window it evicts pages it will need again → thrashing (constant page faults, disk-latency stalls). This is the reuse-distance argument at the page granularity: when the Working set model size exceeds capacity, temporal locality can no longer be captured, exactly as caused cache misses. See Virtual memory for the paging mechanism. The fix: give it frames, or restructure the code (tiling!) to shrink the working set below 150.


Recall Quick self-check (cloze)

Reuse distance hits (fully-assoc LRU) when ::: (cache holds blocks). Spatial locality dies when stride reaches ::: the block size . Largest tile with 3 resident tiles ::: . Blocking cuts matrix 's miss rate from 100% to ::: about . Real caches add which extra miss type beyond the LRU model? ::: conflict misses (from set-associativity).