Cache lines and useful locality
Count the bytes a program consumes and distinguish reuse from merely adjacent addresses.
On this page
The question for today
What you’ll understand
- Separate a requested element from its transferred cache line.
- Estimate useful-byte efficiency for explicit access patterns.
- Distinguish temporal locality, spatial locality, and working-set size.
An eight-byte load does not necessarily move only eight bytes through every level of a processor's memory hierarchy. Caches organize storage and transfers into blocks called lines, creating an opportunity to reuse neighboring bytes. That opportunity becomes a benefit only when the program actually consumes those bytes or returns before eviction. Follow a concrete access trace rather than assuming that a small allocation or a contiguous container automatically produces effective locality.
The element and the transfer unit
Imagine a hypothetical cache with 64-byte lines and an aligned array of eight-byte values. Each complete line contains eight array elements. A sequential read can consume all eight after the first fill, provided the line stays available. A read that takes one element from each distinct line consumes only one eighth of each fetched line. The cache-line size here is a declared example parameter, not a universal property of every processor or every cache level.
Alignment changes which line contains an element. An access spanning a boundary can touch two lines even when its payload is small. Distinguish the processor instruction's access size from the bytes transferred at the particular hierarchy boundary under discussion. Compression, write policies, speculative accesses, and prefetching can change traffic further. State whether an estimate counts demand fills, all memory-controller traffic, or only useful application bytes; those quantities answer different questions.
Reuse has a distance
Temporal locality means returning to previously used data; spatial locality means using nearby data that arrives together. Neither is a yes-or-no property of an entire program. A matrix multiply may reuse a small tile intensely while streaming through another matrix. A pointer chase may revisit the same nodes only after touching a much larger intervening set. The order of access therefore matters as much as total allocation size when deciding whether useful data can remain close to execution.
Blocking reorganizes work so a manageable subset receives repeated use before the program moves on. Count inputs, outputs, and temporary storage together when choosing a block. Fitting their nominal bytes into a cache is necessary for some reuse plans but does not guarantee residency: set conflicts, competing threads, replacement, and shared cache capacity still matter. Verify the transformation preserves dependencies and numerical requirements before interpreting any speed improvement as a memory benefit.
Measure a claim about locality
Compare two traversal orders with identical useful work and data, while recording dimensions, layout, initialization, and thread placement. Keep correctness checks outside the timed region when appropriate, but prevent the compiler from removing the computation. Test more than one working-set size: a change that helps a tiny hot loop can lose when extra packing or copying dominates. Elapsed time is the outcome; misses and transferred bytes provide supporting evidence about the mechanism.
After the spatial byte calculation, inspect temporal reuse in the cache lab. It starts empty, uses fully associative LRU replacement, and requests address identifiers (i×s) mod 16 for N=min(4096,16+floor(2t)) requests. Reset, set capacity C=4 and stride s=4, then predict the time-zero readout: the four-address period gives four misses and twelve hits among sixteen requests. Step adds one request, which hits. For a separate run, Reset and re-set C=4, s=3: predict a sixteen-address period and sixteen misses at time zero. The first working set fits; the second exceeds four entries and continues evicting entries before reuse. Control edits recompute the trace at retained elapsed time, so Reset separates comparisons. These are abstract address identifiers, not CPU cache lines or byte addresses. The toy has no set mapping, spatial line fills, prefetching, or measured processor timing.
Work it through
Reading one value from each line
An aligned array contains 8,192 eight-byte values. Assume cold reads, 64-byte line fills, no prefetching, and no repeated eviction. Compare consuming every value with consuming only indices 0, 8, 16, and so on.
The full payload is 8,192×8=65,536 bytes. Eight values fit in each declared 64-byte line, so the full traversal intersects 1,024 lines.
The strided traversal consumes 1,024 values, or 8,192 useful bytes. It still touches those 1,024 distinct lines, requiring 65,536 bytes of demand fills under the stated model.
Useful-byte efficiency is 65,536/65,536=1 for the full traversal and 8,192/65,536=0.125 for the strided one. These are traffic ratios, not predicted runtime ratios; instruction count and overlap differ.
Spatial locality is useful only when the fetched neighbors contribute to the workload. The strided example uses 12.5% of its demand-fill bytes.
The living notebook
Explore the model
Interactive lab · cache
Temporal reuse in a four-entry LRU cache
Reset, set C=4 and s=4, and predict four misses plus twelve hits at time zero; Step adds one hit. For a separate comparison, Reset and re-set C=4, s=3: predict sixteen misses at time zero. This empty-start fully associative LRU toy requests (i×s) mod 16; it does not model CPU cache-line geometry, set conflicts, prefetching, or benchmark performance.
- Hits / requests
- 0 / 16
- Hit rate
- 0%
- Address-stream period
- 16 addresses
A fully associative LRU cache starts empty and requests address (i·s) mod 16. Hits move an address to the newest position; misses evict the oldest when full. Playback starts with 16 requests and adds two per second, capped at 4096 explicitly simulated requests. This toy stream is not a processor-cache benchmark. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.
Look forA repeated address period that fits the toy capacity permits reuse; a larger period can evict entries before their next use.
Pause & reason
Check your understanding
Your turn
Try it yourself
A 96-byte contiguous range begins 48 bytes into a 64-byte line. Count intersected lines, then calculate useful-byte efficiency if each line is filled once and no other bytes are consumed.
Take a moment to make a prediction or work it out on paper. Open a hint when you need one.
Give me a hint
Locate the final byte's line, or use the starting offset plus the range length.
Compare with the solution
The range covers offsets 48 through 143 relative to the first line's start, touching three lines. Their fills transfer 192 bytes, so efficiency is 96/192=0.5. An equally sized aligned range touches only two lines. This calculation isolates alignment and demand fills; it is not a claim that the two implementations have a fixed two-to-three latency ratio.
Keep the idea
Take it with you
- Requested bytes and transferred bytes belong to different accounting layers.
- Reuse depends on the access order and intervening working set.
- Validate locality hypotheses with unchanged useful work and explicit traffic definitions.
Read further
Original sources and deeper explanations to follow at your own pace.
Optional progress, saved in this browser when available. Revisit any lesson at any time.
Lesson not marked complete.