Principle of locality (temporal - spatial)
Overview
The principle of locality is the fundamental observation that programs access a relatively small portion of their address space at any given time. This predictable access pattern is the entire reason cache hierarchies work — without locality, caching would be useless.
If programs accessed memory randomly, every access would be equally likely. But real programs have structure, and structure creates predictability.
Two Flavors of Locality
Temporal Locality
Why this happens:
- Loop counters:
for(int i=0; i<n; i++)— variableiaccessed every iteration - Function calls: The same function code executes multiple times
- Frequently-used variables: Accumulators, status flags, pointers
Time scale: Milliseconds to seconds in human terms, thousands to millions of cycles in CPU terms.
where and are accesses to the same address.
Short reuse distance = strong temporal locality.
Derivation of why this matters for caching:
Let's say a cache holds blocks. If the reuse distance for address is :
- If : is still in cache when re-referenced → cache hit
- If : was evicted → cache miss
The hit rate for temporal locality is:
This is why larger caches capture more temporal locality — they can hold blocks with longer reuse distances.
Spatial Locality
Why this happens:
- Sequential instruction execution: Instruction at address
0x1000is followed by0x1004 - Array traversal:
a[0], a[1], a[2], ...are contiguous in memory - Struct field access: Fields of a struct are adjacent
- Stack operations: Push/pop access nearby stack addresses
where is the -th memory address accessed.
Stride = 1 word → perfect spatial locality (sequential access) Stride = 0 → pure temporal locality (same location) Large stride → poor spatial locality (jumping around)
Derivation of cache block benefit:
A cache block holds bytes. When we fetch address , we get .
If the program accesses consecutive addresses starting at (stride 1), we pay one miss but get hits:
As (where is word size), hit rate .
Example: 64-byte cache block, accessing 16 consecutive words (4 bytes each):
- words, bytes = 16 words
- Hit rate =
This is why larger cache blocks exploit spatial locality better — but only up to a point (if stride is large, we fetch useless data).
Working Together: The Locality Combo
int sum = 0; // sum in register
for (int i = 0; i < 1000; i++) {
sum += array[i];
}Temporal locality:
- Variable
sum: accessed 1000 times (read it, add to it, store it) - Variable
i: accessed 1000 times - The loop instructions themselves: executed 1000 times
Spatial locality:
array[0], array[1], array[2], ...are contiguous in memory- Fetching
array[0]bringsarray[1..15]into cache block (if 64-byte blocks, 4-byte ints) - Stride = 4 bytes (one integer)
Why each step matters:
- First iteration: Cache miss on
array[0], loads block containingarray[0..15] - Next 15 iterations: Cache hits on
array[1..15]— spatial locality pays off - 16th iteration: Cache miss on
array[16], loadsarray[16..31] - Pattern repeats
Total misses = misses for 1000 accesses → hit rate 93.7% from spatial locality alone.
Add temporal locality: sum, i, and loop code stay in cache (or registers) → hit rate for those is ~100%.
// Row-major storage: A[i][j] at memory address &A + (i*N + j)*sizeof(int)
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
for (int k = 0; k < N; k++) {
C[i][j] += A[i][k] * B[k][j];
}
}
}Access patterns:
A[i][k]: Accesses row sequentially → good spatial locality (stride = 1 element)B[k][j]: Accesses column → stride = elements (jumps across rows)- If , stride = 4000 bytes (assuming 4-byte ints)
- Cache block is 64 bytes → only 1 element used per block fetched
- Terrible spatial locality: 1/16 = 6.25% utilization of fetched data
Why this step is terrible:
- Each
B[k][j]access misses in cache - We fetch 64 bytes but use only 4 bytes
- Bandwidth wasted, performance plummets
Fix: Transpose first, or use blocking (covered in cache optimization).
Quantifying Locality: The 90/10 Rule
This isn't a law of physics — it's a statistical observation across real workloads.
Why?
- Loops dominate execution time (inner loops executed millions of times)
- Hot functions called repeatedly
- Cold code (error handlers, initialization) rarely runs
Derivation:
Let's model a program with instructions, where fraction are "hot" (in loops/frequently called).
If hot instructions execute times on average, and cold instructions execute once:
where is time per instruction.
Fraction of time in hot code:
For 90/10 rule: , solve for :
So if 10% of code executes ~81 times on average, you get the 90/10 rule.
Cache implication: If you can keep that 10% of code and its data in cache, you've optimized 90% of execution time.
Common Mistakes & Misconceptions
Why it's wrong: That's temporal locality! Spatial means accessing nearby addresses (different addresses, but close in the address space).
Steel-man: The confusion arises because both improve cache performance, and in practice they often occur together (e.g., looping over an array has both). But the mechanisms differ:
- Temporal: Keep the same block in cache for reuse
- Spatial: Prefetch adjacent blocks because they'll be needed soon
The fix: Remember the etymology: "Temporal" = time (same address over time), "Spatial" = space (nearby in address space).
Why it's wrong: If your stride is large or access pattern is random, you fetch useless data. Larger blocks also mean:
- Fewer blocks fit in cache (for fixed cache size) → worse temporal locality
- Higher miss penalty (more bytes to transfer)
- Conflict misses increase (fewer blocks = more collisions)
Example:
- Cache size = 32 KB
- Block size = 16 bytes → 2048 blocks
- Block size = 256 bytes → 128 blocks
If your working set has 500 distinct blocks with good temporal locality, the 16-byte blocks win (can hold all 500), but 256-byte blocks can only hold 128 → many evictions.
The fix: Optimal block size depends on stride distribution and working set size. Typical sweet spot: 32-128 bytes.
Why Locality Enables the Memory Hierarchy
If these bets pay off, you get:
- Fast memory (cache) holds frequently used data
- Slow memory (DRAM) holds everything else
- Average access time approaches cache speed, not DRAM speed
Derivation of average access time:
Let = hit rate (fraction of accesses in cache), = cache access time, = DRAM access time.
Why both terms? Cache is checked first (always pay ), then on miss you also pay .
Simplify:
Example: ns, ns, :
~17× faster than DRAM (which needs ns per access) — all because locality makes .
If no locality (), ns → cache is useless overhead.
Connections
- Cache organization fundamentals — how caches exploit locality via blocks and replacement policies
- Cache mapping strategies — direct-mapped vs. set-associative, trading off temporal vs. spatial conflicts
- Working set model — the formalization of locality as "recently used pages"
- Loop blocking and tiling — compiler transformations to improve locality
- Virtual memory — locality across the entire address space enables demand paging
Recall Explain to a 12-year-old
Imagine your desk is your cache, and your bookshelf across the room is main memory. When you're doing homework:
Temporal locality: You keep using the same pencil over and over. Instead of walking to the shelf to get it each time, you just leave it on your desk because you know you'll need it again in 30 seconds. That's temporal — "I just used this, I'll probably use it again soon."
Spatial locality: When you grab your math textbook from the shelf, you also grab your math notebook because it's right next to it, and you'll need it in a minute anyway. That's spatial — "If I need this thing, I probably need the things next to it too."
Caches work the same way. The computer keeps recently-used stuff close (temporal) and grabs nearby stuff together (spatial) because that's what programs do — they're not random, they follow patterns like you doing homework.
Space → Spatial is about SPACE and ADJACENCY (nearby addresses)
Simply: "Temporal = Time, Spatial = Space"
#flashcards/hardware
What is the principle of locality?
Define temporal locality
Define spatial locality
What is reuse distance?
What is stride in memory accesses?
Why do larger cache blocks help spatial locality?
Why do loops create temporal locality?
Why do arrays create spatial locality?
What is the 90/10 rule?
Average memory access time formula
Why can large cache blocks hurt performance?
What happens if a program has no locality?
Concept Map
Hinglish (regional understanding)
Intuition Hinglish mein samjho
Dekho yaar, principle of locality ka core idea bahut simple hai — programs kabhi bhi memory ko randomly access nahi karte. Woh patterns follow karte hain. Jab tum ek loop chalate ho ya ek array traverse karte ho, tab CPU ek chhote se memory portion ko baar-baar ya nearby locations ko access karta hai. Yeh predictability hi wajah hai ki caches kaam karte hain. Isko do flavors mein baanto: temporal locality matlab jo memory location abhi access kiya hai woh jaldi hi phir access hoga (jaise loop counter i har iteration mein), aur spatial locality matlab agar tumne address X access kiya, toh X+1, X+2 bhi jaldi access honge (jaise array elements jo memory mein contiguous hote hain).
Ab yeh matter kyun karta hai? Kyunki main memory (RAM) slow hoti hai, aur cache fast hoti hai but chhoti hoti hai. Locality ki wajah se hum smartly predict kar sakte hain ki agla kaunsa data chahiye hoga, aur usko pehle se cache mein rakh dete hain. Temporal locality ke liye, agar ek address ka reuse distance (do accesses ke beech mein kitne unique addresses aate hain) cache size se chhota hai, toh woh data cache mein mil jayega — cache hit! Isliye badi caches zyada temporal locality capture karti hain. Spatial locality ke liye, jab hum ek address fetch karte hain, toh pura cache block (jaise 64 bytes) le aate hain, toh nearby data automatically aa jata hai — ek miss, phir bahut saari hits.
Practical example lo: array sum ka loop. Yahan sum variable baar-baar access hota hai (temporal), aur array[i] elements sequentially access hote hain (spatial) — dono localities ek saath kaam kar rahi hain, aur isi wajah se performance boost milta hai. Yaad rakho, agar programs mein locality na hoti, toh caching bilkul useless hoti aur har memory access equally slow hota. Isliye yeh concept memory hierarchy ki poori foundation hai — jab bhi tum efficient code likhna chahoge, locality ko dhyan mein rakhna future mein bahut kaam aayega.