Architectures · Chapter 3 of 16 · CPUs

Caches and coherence

Main memory is far away and slow, so a CPU keeps copies of recent data in small, fast memories close by. When several cores share data, they have to agree on which copy is current.

Caches work because programs reuse recent data and touch nearby addresses. L1, L2 and L3 caches trade size for speed, and a TLB speeds up address translation. Coherence protocols keep the copies held by different cores consistent when one of them writes.

Locality, cache organization (sets, ways and lines), the three Cs, replacement and write policies, average memory access time, prefetching, TLBs and virtually indexed caches, the MESI protocol, snooping versus directories, false sharing, and memory consistency models.

A processor core can do a simple sum in less than a billionth of a second. But fetching a number from the computer’s main memory takes hundreds of times longer. If the core waited every time, it would sit idle almost all day.

So chips keep a small, fast memory right next to each core. It is called a . It holds copies of the data the core used recently. Most of the time, what the core needs next is already there.

This chapter shows how a cache decides what to keep. It also covers a tricky problem. A chip with many cores has many caches. When one core changes a number, the other copies must not go stale.

A modern core can finish several instructions per clock cycle, but a load that must go all the way to main memory () takes around 100 nanoseconds. MIT’s architecture course puts the gap plainly: a four-issue core at 2 GHz could run 800 instructions in the time of one DRAM access. A is a small, fast memory, built from on the processor die, that keeps copies of recently used data so most loads never make that trip.

Caches are invisible to the program: the hardware decides what to keep. They form the middle of the , from a few registers inside the core, through two or three levels of cache, out to gigabytes of DRAM. This chapter covers:

  • Why caches work: programs reuse recent data and touch nearby addresses.
  • How they are organized: lines, sets and ways, and how an address finds its line.
  • What a miss costs and the policies that reduce it: replacement, writes and prefetching.
  • Address translation and the TLB, which every access goes through.
  • Coherence: keeping the copies in many cores’ caches in agreement.

It follows Instructions and pipelines and Branch prediction and out-of-order execution, which assume loads usually hit in a cache. How the SRAM cell itself works is in Memory cells.

You know a program is a stream of loads, stores and arithmetic. The architect’s problem is that a load-to-use latency of a few cycles is possible only for a few tens of kilobytes: Intel lists 4 cycles for a 32 KB L1, 12 for a 256 KB L2 and 44 for the shared L3 on one recent client core, against about 80 ns (hundreds of cycles) for DRAM. Caches exploit locality to make the average access look like the fast case. Four topics organize the chapter:

  • Organization and policy. Line size, associativity, replacement, write and inclusion policies, and the three Cs as the language for what each choice fixes.
  • Latency arithmetic. AMAT composed recursively across levels, and why cores and prefetchers turn it into a question of memory-level parallelism.
  • Translation. TLBs, page walks and the virtually indexed, physically tagged L1.
  • Coherence and consistency. MESI, snooping versus directories, false sharing, and a brief look at the memory models of x86, Arm and RISC-V.

Product numbers come from vendors’ and open projects’ own documents, x86 (AMD, Intel), Arm and RISC-V side by side, in dated boxes. The same principles shape GPU memory systems, covered in SIMT and GPUs, and the AI-specific limits of HBM and the KV cache in The memory wall.

closer · smaller · fasterfarther · larger · slowerRegisterin the core1 cycleL1 cacheper core4 cyclesL2 cacheper core12 cyclesL3 cacheshared, on chip44 cyclesDRAMoff chip≈ 320 cycles
Units

Tap a level. Each level down is larger and slower; a cache keeps recently used data in the fast levels.

Fastest load-to-use latency from each level. Cache numbers: Intel’s published figures for one core design. DRAM: about 80 ns, shown at an assumed 4 GHz.Share freely with credit: ‘Figure from chipfieldguide.com’

A cache is small. So how can it hold what the core needs? It works because programs have two habits. This is called .

  • Reuse. A program uses the same things again and again. A loop runs the same steps many times and keeps adding to the same total.
  • Nearby use. A program uses things that sit next to each other. A list is read one item after the next.

Caches use both habits. They keep what was just used, for reuse. And they fetch a whole block of neighbors at once, for nearby use. That block is called a .

Some programs break the habits. Following a chain of links that jump all over memory is one example.

Caches rely on two empirical properties of real programs, called :

  • Temporal locality: a location used now is likely to be used again soon. A loop’s instructions run every iteration; a running sum is read and written every iteration.
  • Spatial locality: locations near one used now are likely to be used soon. An array walked in order touches consecutive addresses.

Caches keep recently used data (for temporal locality) and move data in fixed-size chunks called , almost always 64 bytes in today’s CPUs, so one miss brings in the next several words as well (for spatial locality). A loop that sums 4-byte integers misses once per 16 elements and hits on the other 15.

When locality fails

The layout of data matters as much as the algorithm. C stores a two-dimensional array row by row, so walking it by columns jumps a whole row between accesses, and each access needs a different line. Drepper’s classic example is matrix multiplication, whose inner loop walks one matrix down its columns: simply transposing that matrix first, so both are read along rows, cut the run time to 23.4% of the original on a Core 2. Linked lists, hash tables and trees scatter their nodes, so they get little spatial locality, and the next address isn’t known until the current load returns.

Locality is a property of the access stream, and it can be measured. For a line, the reuse distance is the number of distinct other lines touched between two uses. A fully associative LRU cache of CC lines hits exactly the accesses whose reuse distance is less than CC, so the histogram of reuse distances gives the miss rate of every LRU cache size at once (Under the hood shows the algorithm). Spatial locality appears as short strides: a unit-stride walk over 4-byte words touches a new 64-byte line every 16 accesses, a stride of 64 bytes or more touches a new line every time.

In the column walk of the figure, each line is reused only after five others, so the cache must hold all six lines (and not map them to colliding sets) for the reuse to become hits. Drepper’s 1000 × 1000 matrix multiply has the same structure at scale: each pass of the inner loop touches 1,000 lines of each matrix, far more than a 32 KB L1 holds, which is why the transposed version ran 4.3× faster, and why blocking (tiling the loops so a sub-matrix is reused while it is resident) did better still, at 17.3% of the original time. Pointer-chasing workloads are the hard case: their reuse distances are long, their strides irregular, and each load’s address depends on the previous load, which defeats both caches and prefetchers.

code4 instrssumdata↑ address64 B linetime → (48 iterations)3 lines touched · 48 reads
Loop reads

Temporal locality: the loop’s instructions and the sum are reused every iteration. Spatial locality: 48 reads in order touch only 3 lines, and 45 of them fall in the same line as the read before.

Address against time for one loop: instructions, one variable and the data it reads. Shaded bands are 64-byte cache lines. Illustrative addresses.Share freely with credit: ‘Figure from chipfieldguide.com’

A cache can’t be big and fast at the same time. A bigger cache has more to search and longer wires, so it answers more slowly. So chips use a ladder of caches.

  • Level 1 is tiny and very fast. Each core has its own, one for code and one for data.
  • Level 2 is bigger and a bit slower. Each core usually has its own too.
  • Level 3 is big and shared by all the cores.

If level 1 doesn’t have the data, the core asks level 2, then level 3, then main memory. Each step down holds more but takes longer.

Caches are made of a kind of memory called . It is fast but takes a lot of room on the chip. That is why caches stay small.

No single memory can be both large and fast: a bigger array has longer word and bit lines and more to search. So designers add levels, each larger and slower than the one above it. A typical CPU core today has:

  • Registers inside the core, named directly by instructions. They aren’t a cache: the compiler decides what lives there.
  • L1 caches, split into instruction (L1I) and data (L1D), tens of kilobytes each, about 4–5 cycles away. The L1 is sized as the biggest cache whose hit time stays at a cycle or two beyond the address calculation, roughly 16–64 KB.
  • An L2 cache per core, holding both code and data: hundreds of kilobytes to a couple of megabytes, around 12–14 cycles.
  • An L3 cache, the , shared by all cores: tens of megabytes, a few tens of cycles.
  • DRAM main memory: gigabytes, about 80 ns when the system is idle.

All the caches are , six transistors per bit, which is fast and logic-compatible but takes far more area per bit than DRAM; the cell and its trade-offs are covered in Memory cells.

Do the levels keep the same data?

Designs differ in whether a line in an inner cache must also be in the outer one:

  • Inclusive: the L3 holds a copy of everything in the L1s and L2s. Intel cores used this up to the Broadwell generation.
  • Non-inclusive: no guarantee either way. Intel moved to this with Skylake; Arm designs and the open-source XiangShan RISC-V core use it too.
  • Exclusive: a line lives in one level at a time, and the L3 fills with lines evicted from the L2s. AMD’s processors work this way.

Inclusion wastes some capacity on duplicates, but it makes coherence simpler, as the coherence section explains.

Each level filters the traffic seen by the next. For a two-level hierarchy, distinguish the local miss rate of the L2 (misses ÷ accesses that reach the L2) from the global miss rate (L2 misses ÷ all CPU accesses), and normalize by work with misses per instruction. A deep hierarchy can have an L2 with a poor-looking local miss rate that is still doing its job, because the L1 has already removed the easy hits.

The inclusion choice trades capacity against coherence cost. With an inclusive LLC, a lookup there answers “does any core hold this line?”, so external requests need not probe the private caches. The price is duplication and inclusion victims: evicting a line from the LLC must also invalidate it above, even if a core is actively using it. Jaleel et al. found that these victims, not the lost capacity, account for most of the gap between inclusive and non-inclusive performance. Intel’s Skylake server parts made the switch explicit: a larger 1 MB, 16-way private L2 (14 cycles) and a smaller, non-inclusive L3 of 1.375 MB per core, which can behave like a victim cache of the L2s. AMD’s exclusive L3 holds lines swapped out of the L2s and keeps shadow copies of each L2’s tags, so it can still answer coherence questions inside a core complex. Non-inclusive and exclusive designs need that separate tracking (shadow tags, snoop filters or directories), because a miss in the LLC no longer proves that no core holds the line.

A victim cache is the small-scale version of the same idea: a few fully associative entries holding lines just evicted from a direct-mapped L1, which removes many conflict misses at almost no hit-time cost.

processor diecore 0L1I32 KBL1D32 KBL21 MBcore 1L1IL1DL2core 2L1IL1DL2core 3L1IL1DL2L3, shared8 MB in 4 slicesDRAMgigabytes, off chipunique capacity8 MB
Inclusion

Inclusive: every line in an L1 or L2 is also in the L3. The L3 alone can answer “does any core have this line?”, but its space holds duplicates.

Four cores, each with split L1s and a private L2, sharing an L3. Tap a level; the orange dot shows where copies of one line used by core 0 live. Illustrative sizes.Share freely with credit: ‘Figure from chipfieldguide.com’

The cache stores blocks, and each block keeps a label saying which part of memory it came from. When the core asks for an address, the cache must find the right block quickly.

Searching every spot would be slow. So the address itself says where to look. Part of it picks a group of spots, called a set. Part of it is the label to check. The last part picks the byte inside the block.

  • One spot per block. The fastest kind. But two blocks that want the same spot keep pushing each other out.
  • Anywhere at all. No fights over spots, but every label must be checked each time.
  • A few spots per block. The usual middle path. This is a cache.

A cache of CC bytes with LL-byte lines holds C/LC/L lines. Each stored line carries a tag (the upper address bits identifying which memory line it is), a valid bit and, for a write-back cache, a dirty bit. An address is split into three fields:

tag⏟compare ∣ index⏟log⁡2(sets) bits ∣ offset⏟log⁡2L bits\underbrace{\text{tag}}_{\text{compare}}\ \Big|\ \underbrace{\text{index}}_{\log_2(\text{sets})\ \text{bits}}\ \Big|\ \underbrace{\text{offset}}_{\log_2 L\ \text{bits}}

The index picks a set; the tags of every line in that set are compared with the address’s tag.

  • Direct-mapped: one line per set. Fast and simple, one comparison, but two lines whose addresses share an index evict each other even if the rest of the cache is empty. MIT’s lecture names the worst case: a stride equal to the cache size, which sends every access to the same set.
  • Fully associative: one set containing every line. No index bits, no conflicts, but a comparator per line, practical only for small structures like TLBs.
  • : NN lines (ways) per set. An NN-way lookup reads NN tags in parallel. L1 and L2 caches are typically 8- to 16-way; AMD’s Zen 4, for example, has an 8-way 32 KB L1 data cache and a 16-way L3.

A worked example: a 32 KB, 8-way cache with 64-byte lines has 32,768/(64×8)=6432{,}768 / (64 \times 8) = 64 sets, so 6 offset bits, 6 index bits, and the remaining upper bits as the tag. Doubling the ways at the same size halves the sets: one bit moves from the index to the tag.

A set-associative lookup is a decode of the index, a parallel read of NN tags and NN data words, NN comparators and an NN-to-1 multiplexer driven by the hit signals. Reading all ways in parallel keeps latency down but spends energy on N−1N - 1 useless data reads; larger caches often read tags first and then only the matching way (serial lookup), or predict the way. Tag storage is not free: for a 48-bit physical address, a 32 KB, 8-way, 64 B cache stores 36-bit tags plus state for 512 lines, about 7% on top of the data.

Index functions need not be the plain low-order bits. Power-of-two strides collide in plain-indexed caches (matrices with power-of-two row lengths are a classic trap), and hashing upper address bits into the index spreads them out at the cost of a little latency and a larger tag. Large shared caches are also split into slices spread across the die, so each address has a home slice somewhere on the on-chip network.

Where the index comes from interacts with translation. If index plus offset fit inside the page offset (12 bits for 4 KiB pages), the L1 can be indexed with virtual-address bits while the TLB translates the tag in parallel: the virtually indexed, physically tagged (VIPT) arrangement. That constraint, capacity ≤ page size × ways, is one reason L1 caches grew in ways rather than sets (the Under the hood section works the arithmetic).

12-bit address001010100100tag1index5offset416 lines0123456789101112131415=1 tag compare
Associativity

0x2a4: offset 4 (5 bits) picks the byte in the 32-byte line; index 5 (4 bits) picks the set; tag 1 (3 bits) is stored and compared. The line may live in set 5, its only line: 1 tag comparison per lookup.

A 512-byte cache of sixteen 32-byte lines and 12-bit addresses. The address splits into tag, index and offset; associativity trades index bits for tag bits and comparators.Share freely with credit: ‘Figure from chipfieldguide.com’

When the cache has what the core asked for, that is a hit. When it doesn’t, that is a , and the data has to come from farther away. Misses happen for three reasons:

  • First time. The block was never used before. No cache can avoid this.
  • Too small. The program uses more than the cache can hold, so blocks get pushed out before they are used again.
  • Crowded spot. There was room elsewhere, but blocks fought over the same few spots.

What matters is the average wait. Say the cache answers in 4 ticks, misses 1 time in 20, and a miss costs 100 more ticks. The average is 4 + 100 ÷ 20, which is 9 ticks. Missing just a bit more often makes the average jump.

A fetches the whole line from the next level. Misses fall into the :

  • Compulsory (cold): the first reference to a line. They would happen even in an infinite cache. Larger lines and prefetching reduce them.
  • Capacity: the cache is too small for the data the program reuses. They would happen even in a fully associative cache with perfect replacement. Only more capacity, or a smaller working set, helps.
  • Conflict: lines collide in a set while other sets have room. They disappear with full associativity; more ways reduce them.

Multicore chips add a fourth kind, coherence misses, when another core’s write invalidated the line; they are covered below. The performance measure is :

AMAT=thit+m×tmiss\mathrm{AMAT} = t_{\text{hit}} + m \times t_{\text{miss}}

With a 4-cycle hit, a 5% miss rate mm and a 100-cycle penalty, AMAT is 4+0.05×100=94 + 0.05 \times 100 = 9 cycles; at 10% it is 14. Because the miss penalty is so much larger than the hit time, a few points of hit rate matter more than a cycle of hit time. With several levels the formula nests: the L1’s miss penalty is the L2’s own AMAT, and so on down to DRAM.

The three Cs are defined operationally, by simulation: count compulsory misses with an infinite cache; capacity misses are the additional misses of a fully associative LRU cache of the same size; conflict misses are what remains for the real organization. The definitions are relative to LRU, so a policy better than LRU can produce “negative” conflict misses, and a real cache can occasionally hit where the fully associative LRU reference misses. The sim uses exactly this classifier.

For a two-level hierarchy:

AMAT=tL1+mL1(tL2+mL2local tmem)\mathrm{AMAT} = t_{\mathrm{L1}} + m_{\mathrm{L1}}\left(t_{\mathrm{L2}} + m_{\mathrm{L2}}^{\text{local}}\, t_{\text{mem}}\right)

With Intel’s Skylake client figures (4-cycle L1, 12-cycle L2) and illustrative rates of 5% L1 misses, 30% local L2 misses and a 300-cycle memory access, AMAT is 4+0.05 (12+0.3×300)=9.14 + 0.05\,(12 + 0.3 \times 300) = 9.1 cycles. Two caveats keep this honest. First, AMAT is a latency model; an out-of-order core overlaps independent misses and keeps working past them, so the stall time per miss is less than the penalty (how is the subject of Branch prediction and out-of-order execution). Second, memory latency rises with load as queues fill, so a 300-cycle idle latency is optimistic under bandwidth pressure.

Each lever moves a different C: capacity attacks capacity misses but raises hit time; associativity attacks conflict misses but raises hit time and energy; line size attacks compulsory misses when there is spatial locality, but past a point it raises the miss penalty and fetches unused bytes.

line numbers accessed, in order →04041235601cache: 4 sets × 1 way (set = line mod 4)–set 0–set 1–set 2–set 3
Organization
1 / 12

Step through the accesses. Then switch to fully associative: conflict misses disappear, capacity misses don’t.

A four-line cache and a fixed sequence of line numbers. Each miss is labeled with its C. Direct-mapped puts line b in set b mod 4.Share freely with credit: ‘Figure from chipfieldguide.com’

This is a small cache you can set up yourself. Each square is one spot that can hold a block. Pick a program and watch the cache fill. Blue squares hold blocks. A green outline means the last read was found. A red outline means it had to go to memory.

Start with “Table by rows,” then switch to “Table by columns.” Then try making the cache bigger, or giving each block more spots. Try “Two cores” last, then turn on “Move y away from x.”

A one-level data cache you configure: capacity, line size and associativity. Each cell is one line; cells outlined together form a set. Pick an access pattern and press Play to watch the lines fill, or read the totals for the whole run. Misses are classified into the three Cs (plus coherence misses for the two-core pattern), and AMAT uses a 4-cycle hit and a 100-cycle miss, both illustrative. Things to try:

  • Matrix rows versus matrix columns at 2 KB. Why does the column walk miss every time? Which change fixes it: more capacity or more ways?
  • Two arrays, direct-mapped, then 2-way. What kind of miss disappears?
  • Matrix rows with 16-byte and then 128-byte lines. How does the hit rate follow the line size?
  • False sharing, then padding. Then set 128-byte lines with padding on. Why does the problem come back?

The Expert view adds a replacement-policy choice (true LRU, tree pseudo-LRU, random) and the miss penalty, and shows the tag, index and offset bit widths for 18-bit addresses. Capacity and conflict misses are separated with a fully associative LRU shadow cache, as in the formal definition. Try:

  • Stride with 1,024- or 4,096-byte steps: every stride that is a multiple of the set span lands in one set, and no number of ways short of full associativity helps. Then 4,160 bytes (4,096 + one line): the conflicts vanish. That is the padding trick for power-of-two arrays.
  • Matrix columns at 2 KB, 4-way, with LRU versus random replacement. LRU’s worst case is a loop slightly larger than the set: it evicts exactly the line needed next. Random does better, the effect RRIP was designed to exploit.
  • The two-core pattern with the invalidation count: each write costs one invalidation once the line ping-pongs.

The model is deliberately simple: one level, fixed latencies, no overlap between misses, and no prefetching, so every miss pays the full penalty.

Loading simulation…

When a new block arrives and its spots are all full, one block must go. The rule for picking it is the .

A good rule is “throw out the block used longest ago.” Old blocks are less likely to be needed. Keeping perfect track is costly, so real chips use quick guesses that are almost as good.

Writing raises a second question. When the core changes a number in the cache, when should main memory find out? One way sends every change at once. The other waits until the block is pushed out, then sends it once. That is a cache. It saves many trips when the same number changes again and again.

Replacement. When a set is full, the picks the victim.

  • Least recently used (LRU) evicts the line untouched for longest. It must update state on every access, and exact LRU is practical only for small sets.
  • Tree pseudo-LRU keeps one bit per node of a binary tree over the ways, each pointing away from the most recent use. It was common for 4- to 8-way caches; XiangShan’s 8-way data cache uses pseudo-LRU.
  • FIFO (round-robin) and random need almost no state and are used in highly associative structures.

LRU is not always best. A loop over data slightly larger than the cache is LRU’s worst case: it always evicts the line that will be needed next. Last-level caches therefore use policies that resist such “thrashing” and one-time scans; RRIP, for example, uses 2 bits per line and beat LRU by 4–10% on average in its authors’ tests.

Writes. A write-through cache sends every store to the next level; simple, but every store costs bandwidth. A cache updates only itself and sets a dirty bit; the line is written back once, when evicted. Most CPU caches are write-back, and on a store miss they write-allocate: fetch the line first, then update it. Intel’s cache tables list every level as write-back.

Exact LRU for an NN-way set must encode a permutation, ⌈log⁡2N!⌉\lceil \log_2 N! \rceil bits (5 for 4 ways, 16 for 8), updated on every hit. Tree PLRU uses N−1N - 1 bits: each access sets the bits on its root-to-leaf path to point away, and the victim is found by following the pointers. It approximates LRU well for small NN and can diverge from it, which the figure’s sequence shows (PLRU happens to beat LRU there). Set dueling dedicates a few sets to each of two policies, counts their misses, and applies the winner to the rest of the cache. RRIP stores a 2-bit re-reference prediction per line, inserts new lines with a “distant” prediction so scans pass through without displacing the working set, and promotes lines on hits; its dynamic variant duels two insertion policies.

Write policy interacts with the rest of the system. Write-back caches hold the only valid copy of dirty data, which is what makes the Modified state of coherence protocols necessary and what ECC must protect. Write-through L1s (with a write-back L2 behind them) simplify coherence and error recovery, since the L2 always has current data, at the cost of store bandwidth absorbed by a write buffer. Streaming stores that will never be read back are better written around the cache entirely; x86’s non-temporal stores do this.

ABCDABEABCDErank = recency (1 = most recent); evict rank 4–way 0–way 1–way 2–way 3misses so far: LRU 0 · PLRU 0 · FIFO 0
Policy
Victim
1 / 13

One 4-way set. Step through the accesses; compare which way each policy evicts and the running miss counts.

Replacement: one 4-way set under three policies. Writes: one line stored six times, then evicted, under write-through and write-back.Share freely with credit: ‘Figure from chipfieldguide.com’

A cache only helps after the first miss. tries to beat that. The chip watches which blocks a program reads and guesses which ones come next. Then it fetches them early.

Reading a list in order is easy to guess: next comes the next block. Jumping by the same amount every time is easy too. Random jumps can’t be guessed, and wrong guesses waste time and space.

Timing matters. A guess that arrives late still makes the core wait a little. So good prefetchers fetch several blocks ahead.

Hardware watch the stream of misses and fetch lines before they are requested. Drepper describes the common behavior: a prefetcher waits for two or more misses in a recognizable pattern before starting, tracks several streams at once (eight to sixteen in the processors he measured), and stops at page boundaries, because the next physical page is unknown until the next virtual page is translated. The common kinds:

  • Next-line: on an access to line kk, fetch k+1k + 1.
  • Stream: detect ascending or descending runs of lines and keep several lines ahead.
  • Stride: track each load instruction’s address deltas and, when they repeat, fetch the next address in the sequence, which catches walks down a matrix column or through an array of structures.

Vendors document these explicitly. AMD lists stream, stride, “region” and up/down prefetchers at L1 and L2 for its Zen cores. Intel’s manual describes an L2 streamer that fetches the pair line of a 128-byte block and a data prefetcher that stays inside a 4 KB page. Prefetching does nothing for random or pointer-chasing access, and wrong guesses cost bandwidth and evict useful lines. Software can also prefetch explicitly; x86 has prefetch instructions with hints for which level to fill.

A prefetcher is judged by three measures:

  • Coverage: the fraction of demand misses it removes.
  • Accuracy: useful prefetches ÷ prefetches issued.
  • Timeliness: the line must arrive before use, but not so early that it is evicted first.

Timeliness sets the prefetch distance. If each line takes tworkt_{\text{work}} cycles to consume and memory latency is LL, the prefetcher must run d≥⌈L/twork⌉d \ge \lceil L / t_{\text{work}} \rceil lines ahead, which is Little’s law again: requests in flight = latency × rate. The figure’s next-line prefetcher is accurate but late (it is one line ahead when six are needed), so it saves only a sixth of the stall. Distance also costs capacity and miss-handling resources: every in-flight line needs a miss-status holding register, and XiangShan’s data cache has 16.

Stride prefetchers index a table by load PC and store last address, last stride and a confidence counter; Intel notes its IP-based prefetcher distinguishes loads by only the low 8 bits of the instruction address, so two loads in a large loop can alias in the table. Newer designs add patterns (Redwood Cove added an array-of-pointers prefetcher), but the page-boundary limit and the dependence chain of true pointer chasing remain.

coredone at 1120memoryin flightcycles →workstalldemand missprefetch
Pattern
Prefetcher

Every line is a demand miss: 1120 cycles, 960 stalled (86%).

16 line reads with 10 cycles of work each and a 60-cycle miss (illustrative). Top: the core working or stalled. Bottom: fetches in flight; prefetches have dashed outlines.Share freely with credit: ‘Figure from chipfieldguide.com’

Every program on a computer thinks it has the memory to itself. Each one uses its own made-up addresses. This is called . The chip turns each made-up address into a real one before it reads memory.

The translations are kept in tables in memory. Looking one up takes several trips, which would be far too slow for every read. So the chip keeps a tiny list of recent translations, called a . Almost every time, the answer is already there.

Memory is split into pages, often 4,096 bytes each. A translation covers a whole page at once.

Programs use . The operating system keeps, for each process, a page table mapping virtual pages (usually 4 KiB) to physical pages; the hardware translates every access. Translation isolates processes, lets memory be shared and moved, and lets a program use more address space than there is memory.

A single flat table would be huge, so page tables are trees. RISC-V’s Sv39 scheme, for example, translates a 39-bit virtual address through a three-level table; each table is one 4 KiB page holding 512 eight-byte entries, so each level consumes 9 address bits, and the low 12 bits pass through untranslated. Sv48 adds a fourth level. x86-64’s standard four-level paging translates 48-bit addresses through tables of 512 entries in the same way, and a five-level mode extends it to 57 bits.

Walking four levels means four memory accesses before the real one, so every core caches translations in a (translation lookaside buffer): typically 32–128 highly associative entries, often backed by a larger second-level TLB. Skylake, for instance, has a 64-entry L1 data TLB and a 1,536-entry L2 TLB for 4 KB pages. On x86 a TLB miss is handled by a hardware page walker; some older instruction sets, such as MIPS and Alpha, trapped to the operating system instead.

TLB reach is the memory a TLB can map at once: 64 entries × 4 KiB = 256 KiB. Programs with bigger working sets miss in the TLB even when their data is in cache, which is why larger pages exist: x86-64 supports 2 MB and 1 GB pages, and RISC-V 2 MiB megapages and 1 GiB gigapages.

The TLB sits on the L1 hit path, so its latency is hidden by overlapping it with the cache index. In a virtually indexed, physically tagged L1 the set is chosen with untranslated low bits while the TLB produces the physical page number, which is then compared with the tags. This works without aliasing only if index + offset bits ≤ page-offset bits. A 32 KB, 8-way L1 with 64-byte lines uses exactly 12 bits; Zen 4’s 32 KB 8-way L1D fits. Larger L1s break the rule and must handle synonyms (two virtual addresses for one physical line landing in different sets): XiangShan’s 128 KB, 8-way data cache does so with help from its L2. Fully virtual caches avoid translation on a hit but need address-space IDs in their tags and suffer the same aliasing problem.

Walk cost is the expensive part of a TLB miss. Each of the four levels is a dependent load that may itself miss in the data caches, so designs add page-walk caches for upper-level entries and per-page-size TLBs; GPUs pay the same cost, and SIMT and GPUs quotes a measured example. Context switches either flush the TLB or tag entries with an address-space ID. Prefetchers stop at page boundaries partly because crossing one needs a translation that may fault.

virtual address, 48 bitsL4L3L2L1offset 12TLBpage-table walk (memory)L4 tableL3 tableL2 tableL1 tablephysical address → cache?offsetpage-table reads: 0
TLB
Page size
1 / 8

A 48-bit virtual address: the page number (top 36 bits) needs translating; the 12-bit offset passes through unchanged.

Address translation with four-level paging (as in RISC-V Sv48 and x86-64). A TLB hit is quick; a miss walks the page table, one memory access per level.Share freely with credit: ‘Figure from chipfieldguide.com’

A chip with several cores has several caches. Two cores can each keep a copy of the same number. If one core changes its copy, the other copy is now wrong. Reading it would give an old answer.

Keeping all the copies in agreement is called . The usual rule is simple. Many cores may read a block at once, but only one may change it. Before a core changes it, every other copy is thrown away.

Each copy carries a letter that says what kind of copy it is. The common set of letters is M, E, S and I, so the rules are called . You can try them in the figure.

There is a catch. Copies are kept by the block, not by the number. If two cores change different numbers in the same block, the block bounces between them. This is called , and it can make a program many times slower.

Each core has private caches, so several copies of a line can exist. keeps them from disagreeing. A protocol must guarantee two things: writes eventually become visible to all cores (write propagation), and all cores see writes to the same location in the same order (write serialization). Nearly all CPUs use write-invalidate protocols: before a core writes, every other copy is invalidated.

MESI

In the protocol each line in each cache is in one of four states:

  • Modified: this cache has the only copy, and it is newer than memory.
  • Exclusive: the only copy, unchanged. A write can upgrade it to M silently.
  • Shared: one of possibly several clean, read-only copies.
  • Invalid: no usable copy.

A read miss fetches the line in E if nobody else has it, or S if others do (a core holding it in M supplies the data and drops to S). A write needs M: it invalidates every other copy first. The E state exists because private data is commonly read and then written; without it, every such write would need an extra message. AMD’s cores use MOESI, which adds an Owned state so a modified line can be shared without first writing it back to memory.

Snooping or a directory

How do caches learn about other cores’ requests? With , every miss is broadcast on a shared bus and every cache checks its tags. It is simple and fast for a few cores, but the broadcasts grow with core count. A directory instead records, for each line, which caches might hold it, and sends messages only to those. It scales to many cores, at the cost of storage and an extra hop through the directory. Large chips mix the two: AMD’s L3 keeps shadow tags of each L2 in its core complex, and Intel’s multi-socket servers keep a directory in memory. How the cores and sockets are wired together, and NUMA, are covered in Multicore and vector units.

False sharing

Coherence works on whole lines. If two threads write different variables in the same line, each write invalidates the other core’s copy and the line ping-pongs between caches, though no data is actually shared. Drepper measured 390%, 734% and 1,147% overhead with two, three and four threads incrementing counters in one line on a four-socket machine. AMD’s game-developer guide shows a test dropping from 28.6 s to 2.4 s when each thread’s data was aligned to its own 64-byte line. The fix is padding or alignment to the line size, or per-thread data merged at the end.

The invariant MESI maintains is single-writer/multiple-reader per line: at any time either one cache may write (M or E) or any number may read (S). The stable states hide the hard part. Requests are not atomic on a split-transaction bus or a network, so real protocols add transient states (I→S waiting for data, S→M waiting for acknowledgments) and must resolve races between them.

Snooping needs an ordered broadcast medium as the serialization point and a tag lookup in every cache for every miss; both bandwidth and energy grow with the number of caches. Directories (first proposed by Censier and Feautrier in 1978) make the line’s home the serialization point and allow unordered networks. A full-map directory stores a bit per core per line, which grows linearly with cores; practical designs use sparse directories organized as set-associative caches of tracked lines (whose own evictions force invalidations), limited-pointer or coarse-vector encodings, or embed the sharer bits in an inclusive LLC’s tags. A read of a line held modified elsewhere becomes a three-hop transaction (requester → home → owner → requester), which is why such hits cost more: on an older Xeon, an L3 hit took about 42 cycles when the line was clean but about 73 when another core held it modified.

Synchronization stresses coherence hardest. A spin lock built on atomic swap makes every waiting core request the line in M on each attempt; test-and-test-and-set spins on a shared copy and attempts the swap only when the lock looks free, and load-reserved/store-conditional pairs avoid locking the bus.

Coherence is not consistency

Coherence orders accesses to one location. A says how accesses to different locations may appear reordered to other cores. Lamport’s sequential consistency requires a single interleaving that respects every core’s program order. No mainstream ISA promises it: x86 is total store order, where a store can sit in the core’s store buffer while a later load to another address completes; Armv8 and RISC-V RVWMO are weaker still, and code that needs order uses fences (MFENCE, DMB, FENCE) or acquire/release accesses. Under the hood shows the store-buffering test that separates them.

core 0 cacheIInvalidcore 1 cacheIInvalidcore 2 cacheIInvalidcore 3 cacheIInvalidshared bus: requests are broadcast and snooped by allmemory: currentmessages: 0
Core
Protocol

Pick a core and an operation. Each private cache holds line X in one MESI state; the protocol keeps one writer or many readers.

MESI states for one line in four private caches. Snooping broadcasts each miss to every cache; a directory contacts only the caches that hold the line.Share freely with credit: ‘Figure from chipfieldguide.com’
Typical line size
64 bytes
L1 / L2 / L3 load-to-use (one Intel client core)
4 / 12 / 44 cycles
Idle DRAM latency (one Xeon, local)
≈ 82 ns
False-sharing fix in one AMD test
28.6 s → 2.4 s

What these numbers mean:

  • 64 bytes is the size of one block. That is 16 ordinary numbers moved in one trip.
  • 4, 12 and 44 ticks are how long the three cache levels take to answer on one chip. Main memory takes hundreds.
  • 82 nanoseconds is a trip to main memory. That is short for a person, but a core could do hundreds of sums in that time.
  • 28.6 to 2.4 seconds: moving each core’s number into its own block made one test about 12 times faster.

Three readings of the table. First, the L1 data cache has stayed in a narrow 32–128 KB band for years while the L3 has grown to hundreds of megabytes: L1 size is pinned by hit time, L3 size by die area and cost. Second, the private L2 has grown to 1–2 MB per core, absorbing more of the L1 misses before they reach the shared, slower L3. Third, the doesn’t decide any of this; x86, Arm and RISC-V cores make similar choices for similar reasons.

Published latencies for three x86 parts and one Arm core show the ladder in cycles:

LevelPentium M (2003-era)Skylake clientIce Lake clientArm Neoverse V2
L1 data≈ 345 (48 KB)4 (64 KB, integer loads)
L2≈ 141213 (512 KB)10 (1 or 2 MB)
L3none44not listednot listed
Main memory≈ 240≈ 82 ns idle on a Xeon, local nodenot listed

Sources: . XiangShan’s documentation gives 3 cycles to read its L1 data array.

Beyond the headline numbers: Intel’s Skylake client L1 sustains about 81 bytes per cycle of its 96-byte peak and the L3 about 18 of 32, so bandwidth falls faster than capacity rises. Remote memory costs about 153 ns against 82 ns local on a two-socket Xeon, the NUMA gap discussed in Multicore and vector units. The EPYC 9684X in the cited comparison carries 1,152 MB of L3, ten times either of the other two chips. For the hardware cost of SRAM at advanced nodes, and why AI accelerators chase even larger on-chip memories, see The memory wall and Wafer-scale and SRAM-heavy designs.

Every choice in a cache helps one thing and hurts another.

  • Bigger caches miss less, but they answer more slowly and take more of the chip.
  • More spots per block means fewer fights over spots, but more labels to check each time, which costs time and power.
  • Bigger blocks bring more neighbors per trip, but waste space when the neighbors aren’t used.
  • Guessing ahead hides waiting when the guesses are right, and wastes trips when they are wrong.

That is why chips use several levels. A tiny, quick cache sits closest. Bigger, slower ones sit behind it.

You getYou give up
Larger capacity: fewer capacity missesLonger hit time, more area and leakage
More associativity: fewer conflict missesMore tag comparisons per access: energy and hit time
Larger lines: fewer compulsory misses, less tag overheadHigher miss penalty, wasted bandwidth, more false sharing
Write-back: far less write trafficDirty data that exists only in the cache; more complex coherence
Inclusive LLC: simple coherence filteringDuplicated capacity and inclusion victims
Aggressive prefetching: fewer stalls on regular accessBandwidth and cache space wasted on wrong guesses
Snooping: simple, low-latency coherenceBroadcast traffic that doesn’t scale to many cores

Common ways software goes wrong

  • Walking data against its layout, such as a row-major matrix by columns.
  • Power-of-two strides and sizes that map everything to a few sets.
  • False sharing between threads’ hot variables.
  • Pointer-heavy structures that defeat both spatial locality and prefetching.
  • Working sets beyond TLB reach, which miss in translation even when the data is cached.
  • Hit time versus miss rate. The L1 is sized to the hit-time budget, not to the miss curve. Every extra pipeline stage of load latency costs on dependent chains, so designers accept a higher L1 miss rate and back it with a fast L2. The figure’s model shows the optimum moving to larger caches as the penalty behind them grows, which is the economic argument for multiple levels.
  • Associativity and the VIPT constraint. To grow an L1 while indexing with page-offset bits, designers add ways rather than sets (capacity ≤ ways × page size), so L1 associativity has risen; going past the limit means handling aliases, as XiangShan’s 128 KB L1 does.
  • Inclusion. Inclusive LLCs filter snoops but suffer inclusion victims; non-inclusive and exclusive LLCs gain capacity but need separate tracking of core-cache contents (snoop filters, shadow tags, directories), which itself costs area.
  • Directory storage. Full sharer vectors grow with core count; sparse or imprecise directories trade storage for extra invalidations and broadcasts.
  • Replacement at the LLC. LRU is fragile under scans and thrashing; RRIP-style insertion policies and set dueling buy several percent of throughput for 2 bits per line.
  • Prefetch aggressiveness. Distance and degree trade timeliness against bandwidth and pollution; on shared caches one core’s prefetches evict another’s data, and prefetchers that stop at 4 KiB boundaries waste the start of every page in long streams.
  • Caches versus scratchpads. Caches make memory management automatic at the cost of tags, coherence and unpredictable timing. GPUs and AI accelerators move part of the hierarchy to software-managed memory, as in SIMT and GPUs and Dataflow and spatial meshes.
4K16K64K256K1Mcapacity (log scale) →cycles0510AMAThit timemiss rate × penaltybest
Ways
Miss penalty

16 KB, 4-way: hit time 2.7 cycles + miss rate 5.2% × 40 = AMAT 4.76 cycles. Lowest AMAT at 32 KB.

Illustrative model: miss rate falls as 1/√size, hit time rises with size and ways. AMAT is U-shaped, so the first level stays small.Share freely with credit: ‘Figure from chipfieldguide.com’

This part goes deeper, into the math, models and algorithms behind the chapter. It’s written for the Expert level.

1. Classifying misses: the stack algorithm

Because LRU has the inclusion property (an LRU cache of CC lines always contains what an LRU cache of C−1C - 1 lines contains), one pass over a trace with an LRU stack gives the miss count of every fully associative cache size at once: an access hits in a cache of CC lines exactly when its stack distance (reuse distance) is below CC. The three-C split then needs one extra run of the real organization:

three_cs.txt (illustrative)text
seen   = empty set                # every line ever touched
stack  = empty LRU list           # fully associative reference
real   = cache(size, line, ways)  # the organization under test

for addr in trace:
  ln   = addr / line_size
  d    = stack.distance(ln)       # distinct lines since last use, or inf
  stack.move_to_top(ln)
  if real.access(ln) == HIT:  count.hit++
  elif ln not in seen:        count.compulsory++
  elif d >= lines_in_cache:   count.capacity++
  else:                       count.conflict++
  seen.add(ln)
  1. 1L7Stack distance: the reuse distance of this access.
  2. 2L10First touch: misses in any cache, even an infinite one.
  3. 3L11A fully associative LRU cache of the same size would also miss.
  4. 4L12The reference would hit; the real placement made it miss.

A histogram of dd is the program’s reuse profile: its cumulative sum is the hit-rate curve versus cache size for LRU. The sim implements the classifier with a bounded LRU shadow of exactly the cache’s size, which gives the same answer for that one size.

2. Replacement state

Exact LRU over NN ways needs ⌈log⁡2N!⌉\lceil \log_2 N! \rceil bits per set; tree PLRU needs N−1N - 1. For 8 ways that is 16 bits versus 7; for 16 ways, 45 versus 15. RRIP uses MM bits per line (M=2M = 2 in the paper): insert at 2M−22^M - 2 (“long re-reference”), promote to 0 on a hit, and on a miss evict a line at 2M−12^M - 1, aging the whole set until one exists. The insertion position is what gives scan resistance: a line touched once never climbs above lines with demonstrated reuse.

3. The MESI transitions

mesi.txt (stable states, snooping)text
state  event            action                 next
I      CPU read         BusRd                  E (no sharer) / S (sharer)
I      CPU write        BusRdX                 M
S      CPU read         -                      S
S      CPU write        BusUpgr (invalidate)   M
E      CPU write        -   (silent)           M
M      CPU read/write   -                      M
S      snoop BusRdX     -                      I
E      snoop BusRd      -                      S
E      snoop BusRdX     -                      I
M      snoop BusRd      supply data, write back S
M      snoop BusRdX     supply data            I
M      evict            write back             I
  1. 1L2The sharer signal from the snoop decides between E and S.
  2. 2L6The payoff of E: read-then-write of private data costs one transaction, not two.
  3. 3L11MOESI would go to O here and skip the write-back.

These are the stable states only; a real controller adds transient states for every transition that waits on the bus or network, and the directory version replaces broadcasts with messages to the home node, which forwards or invalidates as its sharer record requires.

4. Memory consistency in one litmus test

The store-buffering (SB) test is the simplest program whose outcome differs between sequential consistency and the models real processors implement:

sb.litmus (illustrative)text
initially x = 0, y = 0

core 0            core 1
x = 1             y = 1
r0 = y            r1 = x

question: can r0 == 0 and r1 == 0 ?
  sequential consistency : no
  x86-TSO                : yes  (each store waits in its core's store buffer)
  Armv8, RISC-V RVWMO    : yes
  with a full fence between the store and the load on both cores : no
  1. 1L8Some store must be first in any single interleaving, so one load sees 1.
  2. 2L9Observed on real Intel and AMD machines.
  3. 3L11MFENCE on x86, DMB on Arm, FENCE RW,RW on RISC-V.

Sequential consistency is Lamport’s definition. x86 permits only this store-to-load reordering (TSO), with MFENCE to forbid it. Armv8’s model, revised to be multicopy-atomic and now specified formally, and RISC-V’s RVWMO are weaker still: other cores may observe a core’s accesses to different addresses out of order, so message-passing code needs release/acquire accesses or barriers; RISC-V offers the optional Ztso extension for code ported from x86. Coherence is still per-location: all of these models guarantee that every core sees the writes to one address in one order.

5. The VIPT limit, worked

With page size PP, line size LL and WW ways, a cache of capacity CC has C/(LW)C / (LW) sets. Index plus offset bits stay within the page offset when

log⁡2CLW+log⁡2L≤log⁡2P⟺C≤W⋅P.\log_2\frac{C}{L W} + \log_2 L \le \log_2 P \quad\Longleftrightarrow\quad C \le W \cdot P.

With 4 KiB pages: 8 ways allow 32 KB, 12 ways 48 KB, 16 ways 64 KB. Zen 4’s 32 KB 8-way L1D sits exactly at the limit. XiangShan’s 128 KB 8-way L1D is four times past it, so two index bits come from the virtual page number and the design resolves the resulting aliases with its L2. Larger pages relax the bound for data that lives in them, but the cache must work for 4 KiB pages too.

Caches are one part of making a single core fast. The next chapter asks a new question. What happens when a chip has many cores, all sharing caches and memory? Read Multicore and vector units next.

Coherence assumed the cores were already there. Multicore and vector units explains why chips became in the first place, how much more cores help (Amdahl’s law), how threads share one core, how cores and their cache slices are joined by on-chip networks, and why memory attached to another socket is slower (NUMA). After the CPUs come GPUs, which hide memory latency with thousands of threads instead of large caches.

The threads left open here continue in Multicore and vector units: the interconnect that carries coherence traffic, NUMA placement, simultaneous multithreading sharing an L1 between threads, and vector units whose wide loads raise the bandwidth each core asks of its caches. For the AI side of the same memory problem, HBM and the KV cache, see The memory wall.

PipelinesbeforeOut of orderbeforeCachesyou are hereMulticorenextGPUsthen

Multicore: The power wall, Amdahl’s law, threads sharing a core, the on-chip network, NUMA and vector units.

The CPU chapters of this guide. Tap a chapter; Multicore is next.Share freely with credit: ‘Figure from chipfieldguide.com’
Novice · 0 of 4 correct
  1. Q1An L1 cache hits in 4 cycles, misses 5% of the time, and a miss costs 100 more cycles. What is the average memory access time?

  2. Q2Two arrays sit exactly 8 KB apart, and a loop reads a[i] then b[i]. In a direct-mapped cache every access misses. What fixes it?

  3. Q3Two threads each increment their own counter, and the counters sit next to each other in memory. It runs far slower than one thread. Why?

  4. Q4What does a TLB store?

Sources

Show Hide 23 sources
  1. Instruction Set Architecture and Caches, MIT 6.5900 Lecture L02Mengjia Yan · MIT Computer Science and Artificial Intelligence Laboratory · 2023A four-issue 2 GHz core could run 800 instructions during one 100 ns DRAM access; temporal and spatial locality; direct-mapped, set-associative and fully associative caches; a stride equal to the cache size is the direct-mapped worst case.
  2. Caches (continued), MIT 6.5900 Lecture L03Mengjia Yan · MIT Computer Science and Artificial Intelligence Laboratory · 2023AMAT = hit time + miss rate × miss penalty; the biggest L1 that keeps hit time at 1–2 cycles (about 16–64 KB); compulsory, capacity and conflict misses after Hill; random, LRU, tree pseudo-LRU and FIFO replacement; local and global miss rates; victim caches; inclusive (Intel up to Broadwell), non-inclusive (Skylake, Arm) and exclusive (AMD) hierarchies.
  3. Modern Virtual Memory Systems, MIT 6.5900 Lecture L04Mengjia Yan · MIT Computer Science and Artificial Intelligence Laboratory · 2023Per-process page tables in memory, hierarchical page tables, TLBs of typically 32–128 highly associative entries, TLB reach (64 entries × 4 KB = 256 KB), x86-64 page sizes of 4 KB, 2 MB and 1 GB, Skylake’s 64-entry L1 data TLB and 1,536-entry L2 TLB, hardware page walks, and virtually indexed, physically tagged L1 caches.
  4. Cache Coherence, MIT 6.5900 Lecture L12Mengjia Yan · MIT Computer Science and Artificial Intelligence Laboratory · 2023Coherence (one location) versus consistency (many locations); write propagation and write serialization; write-invalidate versus write-update; snooping on a shared bus (Goodman, 1983); VI, MSI, MESI and MOESI; broadcast does not scale; false sharing makes a line ping-pong; test-and-test-and-set.
  5. Directory-Based Cache Coherence, MIT 6.5900 Lecture L13Mengjia Yan · MIT Computer Science and Artificial Intelligence Laboratory · 2023Directories (Censier and Feautrier, 1978) message only the caches that might hold a line and scale to many cores, at the cost of storage; sharer bit vectors, sparse directories organized as caches, limited pointers, and directories embedded in an inclusive shared cache.
  6. Memory Consistency Models, MIT 6.5900 Lecture L14Mengjia Yan · MIT Computer Science and Artificial Intelligence Laboratory · 2023Sequential consistency; store buffers let a load pass an earlier store (TSO, as on x86); weaker models also reorder loads and stores; fences restore order where software needs it.
  7. What Every Programmer Should Know About MemoryUlrich Drepper · Red Hat (author’s copy) · 2007Pentium M access times of ≤ 1 (register), ~3 (L1d), ~14 (L2) and ~240 cycles (memory); 64-byte lines; transposing a matrix before multiplying cut run time to 23.4% (a 76.6% speed-up); false sharing cost 390%, 734% and 1,147% with 2–4 threads; hardware prefetchers trigger on two or more misses, track 8–16 streams and stop at page boundaries.
  8. Intel 64 and IA-32 Architectures Optimization Reference Manual, Volume 1 (248966-051)Intel · Intel · 2026Cache tables: Skylake client 32 KB 8-way L1D at 4 cycles, 256 KB L2 at 12, L3 at 44; Ice Lake client 48 KB L1D at 5 cycles, 512 KB L2 at 13; Golden Cove 48 KB L1D, 1.25 MB (client) or 2 MB (server) L2; Skylake server 1 MB 16-way L2 at 14 cycles and a non-inclusive L3; L2 streamer and stride prefetchers that stay within 4 KB pages; idle DRAM latency of about 82 ns local and 153 ns remote; Xeon 5500 L3 hit ~42 cycles versus ~73 when another core holds the line modified; an in-memory directory across sockets.
  9. 5-Level Paging and 5-Level EPT, White Paper (335252-002, revision 1.1)Intel · Intel · 20174-level paging translates 48-bit linear addresses through 4 KB tables of 512 64-bit entries; 5-level paging extends linear addresses to 57 bits.
  10. AMD Ryzen Processor Software Optimization (GDC 2023)John Hartwig and Ken Mitchell · AMD GPUOpen · 2023Zen 4: 32 KB 8-way L1I and L1D, 1 MB 8-way L2 per core, 32 MB 16-way L3 per eight-core complex; MOESI coherence; L3 shadow tags for each L2; stream, stride, region and up/down hardware prefetchers; a false-sharing test that went from 28,598 ms to 2,422 ms with 64-byte alignment.
  11. AWS Graviton Technical Guide (README)Amazon Web Services · GitHub (aws/aws-graviton-getting-started) · 2026Graviton2/3/4/5 (Arm Neoverse N1, V1, V2, V3): 64 KB L1I and L1D per core; 1 MB or 2 MB L2 per core; 32–36 MB shared LLC (Graviton4: 96 cores per socket, 2 MB L2, 36 MB LLC).
  12. Arm Neoverse V2 Core Software Optimization Guide (PJDOC-466751330-593177, issue 3.0)Arm · Arm documentation · 2022Load latencies ‘assume the memory access hits in the Level 1 Data Cache’: 4 cycles for integer loads with immediate or register-offset addressing (5 for literal loads), section 3.8.
  13. Arm Neoverse V2 Platform (Hot Chips 2023)Magnus Bruce (Arm) · Hot Chips 35 · 202364 KB, 4-way L1 data cache; ‘10-cycle load-to-use, 128B/cycle private L2 cache – 1 or 2MB’; the 2 MB, 8-way L2 keeps the latency of the 1 MB one (10-cycle load-to-use).
  14. Microarchitectural comparison and in-core modeling of state-of-the-art CPUs: Grace, Sapphire Rapids, and GenoaJan Laukemann, Georg Hager, Gerhard Wellein · arXiv:2409.08108 · 2024L1/L2/L3 per chip: NVIDIA Grace (Neoverse V2) 64 KB / 1 MB / 114 MB; Intel Xeon Platinum 8470 48 KB / 2 MB / 105 MB; AMD EPYC 9684X 32 KB / 1 MB / 1,152 MB.
  15. NANHU Core (XiangShan documentation)OpenXiangShan · XiangShan open-source processor documentationSecond-generation XiangShan (Nanhu), an open-source RISC-V core: 64 or 128 KB L1I and L1D (4- or 8-way), private 512 KB or 1 MB 8-way non-inclusive L2, shared 2–8 MB 8-way non-inclusive L3, and a cache controller for cache-maintenance operations.
  16. 总体架构 (DCache overview), XiangShan Nanhu design documentationOpenXiangShan · XiangShan open-source processor documentationNanhu’s data cache: 128 KB, 8-way, pseudo-LRU replacement, SECDED ECC, data read out in 3 cycles, 16 miss-status holding registers, and cooperation with the L2 to handle the aliasing its 128 KB size creates.
  17. The RISC-V Instruction Set Manual, Volume II: Privileged Architecture (version 20240411)RISC-V International · RISC-V International (GitHub release) · 2024Sv39: 39-bit virtual addresses, 12-bit page offset, three-level page table of 512 eight-byte entries per page, with 2 MiB megapages and 1 GiB gigapages; Sv48 adds a fourth level.
  18. The RISC-V Instruction Set Manual, Volume I: Unprivileged Architecture (version 20240411)RISC-V International · RISC-V International (GitHub release) · 2024The RVWMO memory model: one hart’s accesses may be observed out of order by others, so multithreaded code uses FENCE or acquire/release atomics; the Ztso extension offers total store ordering for code ported from x86 or SPARC.
  19. x86-TSO: A Rigorous and Usable Programmer’s Model for x86 MultiprocessorsPeter Sewell, Susmit Sarkar, Scott Owens, Francesco Zappa Nardelli, Magnus O. Myreen · Communications of the ACM (authors’ final version, University of Cambridge) · 2010On Intel and AMD x86 multiprocessors both loads in the store-buffering test can return 0, an outcome no interleaving allows; the x86-TSO model, store buffers and MFENCE.
  20. Simplifying ARM Concurrency: Multicopy-Atomic Axiomatic and Operational Models for ARMv8Christopher Pulte, Shaked Flur, Will Deacon, Jon French, Susmit Sarkar, Peter Sewell · POPL 2018 (authors’ copy, University of Cambridge) · 2018ARMv8 has a relaxed memory model; it was revised to be multicopy-atomic and now has a formal concurrency model; DMB barriers in the examples.
  21. How to Make a Multiprocessor Computer That Correctly Executes Multiprocess ProgramsLeslie Lamport · IEEE Transactions on Computers (author’s copy) · 1979Defines sequential consistency: the result is as if all processors’ operations ran in some single order, each processor’s in program order.
  22. High Performance Cache Replacement Using Re-Reference Interval Prediction (RRIP)Aamer Jaleel, Kevin B. Theobald, Simon C. Steely Jr., Joel Emer · ISCA 2010 (author’s copy, MIT) · 2010LRU performs badly when the working set is larger than the cache or when scans pass through; RRIP needs 2 bits per block and beat LRU by 4% (SRRIP) and 10% (DRRIP) on average with a 2 MB LLC.
  23. Achieving Non-Inclusive Cache Performance with Inclusive CachesAamer Jaleel, Eric Borch, Malini Bhandaru, Simon C. Steely Jr., Joel Emer · MICRO 2010 (author’s copy, MIT) · 2010Inclusive caches simplify coherence; their performance loss comes mostly from inclusion victims (lines evicted from core caches to keep inclusion), not lost capacity.