Cache Misses: The Three Cs

A processor requests a byte, and the cache says no. That single event — a miss — can cost a hundred times longer than a hit. This stalls an instruction pipeline that otherwise retires work every cycle. Understanding why a cache misses is the difference between guessing at performance and engineering it. Every miss a program experiences falls into exactly one of three categories. This taxonomy lets architects reason cleanly about cache design. It replaces the mystery of miss rates with a clear black-box-free model. This classification, known as the Three Cs model, separates three distinct effects. It distinguishes cache capacity, cache organization (associativity), and the unavoidable cost of first contact with data. It matters because these three causes respond to completely different fixes. A miss caused by a cache being too small demands more capacity. A miss caused by two hot addresses colliding in the same set demands more associativity. A miss caused by simply never having seen an address before cannot be fixed by the cache at all. It demands larger blocks or prefetching instead. Conflating these causes leads to wasted silicon. Doubling associativity to fix what is really a capacity problem, for instance, buys almost nothing. The Three Cs model builds directly on Fully Associative Caches and the Design Spectrum. That spectrum — from direct-mapped to set-associative to fully associative — is exactly the design lever involved. It trades away conflict misses at the cost of hit-time and hardware complexity. It also depends on Cache Replacement Policies. This is because which "old" line gets evicted matters greatly. It determines whether a given miss is really a capacity effect or an artifact of a poor replacement choice. This article gives you the formal definitions of compulsory, capacity, and conflict misses. It provides a step-by-step method, including a reference simulation technique, for classifying misses experimentally. It also offers a worked numeric trace showing all three types appearing in one address stream. Finally, it gives an honest discussion of how block size, associativity, and capacity interact — sometimes in surprising, non-monotonic ways. It sets up the next natural question: once you know why misses happen, how do you combine hit rate, miss penalty, and access time into a single number that predicts real program performance?

Key takeaways
  • Every cache miss is classified as exactly one of three types: compulsory, capacity, or conflict.
  • Compulsory misses occur on first access to a block and are independent of cache size or organization.
  • Capacity misses occur when the working set exceeds what the cache can hold, even with full associativity.
  • Conflict misses occur when limited associativity forces eviction of blocks that a fully associative cache of the same size would have kept.
  • The standard way to isolate each type is to compare a cache's miss rate against reference caches of infinite size and full associativity.
  • Different fixes target different Cs: prefetching/larger blocks attack compulsory, more capacity attacks capacity, more associativity (or better replacement) attacks conflict — and these fixes often trade off against each other.
The core insight

A cache miss is never just "a miss" — it is compulsory, capacity, or conflict, and each has a distinct cause and a distinct architectural cure.

The idea

A cache miss occurs when a requested memory block is not present in the cache and must be fetched from a lower level of the memory hierarchy. Miss rate — misses divided by total accesses — is a central performance metric, but a raw miss rate alone tells a designer nothing about what to change. The Three Cs model, decomposes total misses into three mutually exclusive, collectively exhaustive categories. A compulsory miss (also called a cold or first-reference miss) happens the very first time a block is touched by the program, since it cannot possibly already be in the cache. Compulsory misses would occur even in an imaginary cache of infinite size with full associativity, because no amount of retained history helps you before you've seen the data once. The only real levers against compulsory misses are increasing block size (so one miss brings in more useful neighboring data) and prefetching (fetching blocks before they're requested). A capacity miss happens when the cache is fully associative but too small to hold the entire working set of the program between reuses. Even with an optimal replacement policy and unlimited placement freedom, blocks that were evicted to make room for others must be re-fetched. Capacity misses isolate the pure effect of cache size. The only cure is more capacity — a bigger cache, a smarter working-set-reducing algorithm, or a memory hierarchy level (like an L2 or L3) that catches what L1 evicts. A conflict miss happens only because the cache does not have full associativity: a block is evicted (and must later be re-fetched) even though a fully associative cache of the same total size would have kept it, because some other block competing for the same limited set of slots pushed it out. Conflict misses are the price paid for cheaper, faster hardware — direct-mapped and low-associativity set-associative caches are simpler and faster to probe, but they restrict where a block can live, causing collisions that a fully associative design would avoid. The cure is more associativity (moving along the Fully Associative Caches and the Design Spectrum) or a better placement/replacement scheme. Formally, for a given cache size CC, block size BB, and associativity AA, define three reference caches:

  • Cache∞\text{Cache}_\infty: infinite size, fully associative (block size BB fixed) — measures only compulsory misses.
  • CacheC,FA\text{Cache}_{C,\text{FA}}: finite size CC, fully associative — measures compulsory + capacity misses.
  • CacheC,A\text{Cache}_{C,A}: the actual cache under study, size CC, associativity AA — measures compulsory + capacity + conflict misses.

Then:

Compulsory=Misses(Cache∞)\text{Compulsory} = \text{Misses}(\text{Cache}_\infty)
Capacity=Misses(CacheC,FA)−Misses(Cache∞)\text{Capacity} = \text{Misses}(\text{Cache}_{C,\text{FA}}) - \text{Misses}(\text{Cache}_\infty)
Conflict=Misses(CacheC,A)−Misses(CacheC,FA)\text{Conflict} = \text{Misses}(\text{Cache}_{C,A}) - \text{Misses}(\text{Cache}_{C,\text{FA}})

These three quantities sum exactly to the total misses of the real cache, by telescoping subtraction — this is the entire mathematical content of the model, and it is exact by construction, not an approximation, given a fixed reference ordering (fully associative caches must use an optimal or LRU-style policy consistently, discussed below).

How it works

Classifying a miss stream requires simulating multiple caches side-by-side on the same address trace and comparing outcomes access-by-access. This is a standard technique in architecture research (trace-driven simulation) and is directly implementable. The key subtlety: to make the three categories well-defined, all three reference caches must use the same replacement policy family (conventionally LRU) so that differences in miss counts are due only to size and associativity, not to different eviction luck. This connects directly to Cache Replacement Policies — the Three Cs model implicitly assumes a "good" (typically LRU or OPT) policy is used uniformly, otherwise a bad replacement choice could masquerade as a capacity or conflict miss. ```pseudocode

# Classify every access in a trace into COMPULSORY, CAPACITY, CONFLICT, or HIT.

# Requires three synchronized cache simulators sharing LRU replacement. function CLASSIFY_MISSES(trace, C, B, A):

# trace: sequence of block addresses (already divided by block size B)

# C: real cache capacity in blocks; B: block size; A: associativity of real cache infiniteCache = new FullyAssocCache(capacity = INFINITY, policy = LRU) capacityCache = new FullyAssocCache(capacity = C / B, policy = LRU) realCache = new SetAssocCache(capacity = C / B, ways = A, policy = LRU) counts = { COMPULSORY: 0, CAPACITY: 0, CONFLICT: 0, HIT: 0 } for addr in trace: hit_inf = infiniteCache.access(addr) # true = hit, false = miss (& insert) hit_cap = capacityCache.access(addr) hit_real = realCache.access(addr) if not hit_real: if not hit_inf: counts[COMPULSORY] += 1 # never seen before, anywhere elif not hit_cap: counts[CAPACITY] += 1 # seen before, but capacity evicted it else: counts[CONFLICT] += 1 # capacity cache kept it; only real cache lost it else: counts[HIT] += 1 return counts ``` ```pseudocode

# A minimal fully-associative / set-associative cache simulator with LRU. class Cache: function init(numSets, ways): self.numSets = numSets # 1 for fully associative self.ways = ways self.sets = array of LRUSet(ways) for each of numSets function access(blockAddr): setIndex = (numSets == 1) ? 0 : (blockAddr mod numSets) s = self.sets[setIndex] if blockAddr in s.tags: s.touch(blockAddr) # move to most-recently-used position return true # HIT else: if s.isFull(): s.evictLRU() # remove least-recently-used tag s.insertMRU(blockAddr) return false # MISS ``` The algorithm's structure guarantees the classification is mutually exclusive: the `if / elif / else` chain checks strictly nested conditions (real cache miss, then infinite-cache status, then capacity-cache status), so exactly one bucket is incremented per real miss.

Figure
The Three Cs as nested cache comparisons on one address stream
Same trace, three reference caches ∞-size, Fully Assoc. Every block ever seen stays forever Misses = COMPULSORY only Size C, Fully Assoc. Only C blocks fit; LRU evicts the rest Extra misses = CAPACITY (evicted) Size C, A-way Set Assoc. Block can only live in 1 of few sets Extra misses = CONFLICT (bumped by same-set addr) Total misses in real cache decompose exactly: Compulsory Capacity Conflict Misses(real A-way, size C) = Compulsory + Capacity + Conflict Compulsory: fixed by larger blocks / prefetch Capacity: fixed by more cache size Conflict: fixed by more associativity / better replacement

Complexity & correctness

Time and space complexity of classification. Given a trace of NN accesses, the classification algorithm runs three cache simulations in parallel. A fully associative cache with LRU can be implemented with a hash map plus a doubly linked list (for LRU order), giving O(1)O(1) amortized time per access for lookup, touch, insert, and evict. A set-associative cache with AA ways does an O(A)O(A) scan (or O(1)O(1) with per-way tag comparators, as in real hardware, but O(A)O(A) in a straightforward simulator) per access. Total time is:

O(N⋅A)O(N \cdot A)

for the full trace (the AA term dominates only the real-cache simulation; the two fully associative caches are O(N)O(N) each). Space is O(U)O(U) for the infinite cache, where UU is the number of unique blocks touched (bounded by NN), and O(C/B)O(C/B) for each finite cache — so overall space is O(N+C/B)O(N + C/B).

Correctness — why the decomposition is exact, not heuristic. The argument rests on a monotonicity / containment lemma: for LRU replacement, if cache XX has less associativity or less capacity than cache YY (with YY's configuration otherwise a "superset" of XX's placement freedom and size), then any block resident in XX at a given time is also resident in YY at that time, provided both are driven by the same access sequence and both use LRU. This is a classical inclusion property: fully associative LRU caches of increasing size are stack-based — the contents of a size-kk cache are always a subset of the contents of a size-(k+1)(k+1) cache at every point in time (Mattson et al.'s stack-distance argument). Consequently:

  • If the infinite cache misses, the block has never been seen — a true compulsory miss, and no cache configuration could have prevented it.
  • If the infinite cache hits but the size-CC fully associative cache misses, the block was seen before but capacity limits (not placement limits) evicted it — by the inclusion property, this eviction is attributable purely to insufficient size, since a fully associative cache already grants maximal placement freedom.
  • If the size-CC fully associative cache hits but the real AA-way cache misses, the only remaining difference between these two caches is restricted placement (some other block mapped to the same set forced an eviction that full associativity would have avoided) — a true conflict miss.

Because the chain of implications is strictly nested (miss in real cache ⇒\Rightarrow check infinite ⇒\Rightarrow check capacity-limited), every miss falls into exactly one bucket, and the buckets sum to total misses by simple arithmetic (each stage subtracts off exactly the previous stage's count). This is why the model is described as exact by construction rather than approximate: it is a definitional partition of a fixed miss count, backed by the inclusion property that guarantees the comparison is apples-to-apples.

Honest caveats. The model assumes a single, unified, fully-associative reference policy (typically LRU) at each stage; the inclusion/stack property holds rigorously for LRU (and other "stack algorithms" like Belady's optimal OPT) but not for all replacement policies (e.g., FIFO is not a stack algorithm and can exhibit Bélády's anomaly, where a bigger cache has more misses — breaking the clean inclusion argument). Real caches also often use pseudo-LRU approximations for hardware cost reasons, which slightly blurs the conflict/capacity boundary in practice, but the model is still the standard analytical tool architects use, with the understanding that it idealizes replacement as true LRU. Also note: the three categories are defined relative to a fixed block size BB and a fixed reference "infinite cache" with that same block size. Changing block size changes what counts as compulsory (larger blocks reduce compulsory misses via spatial locality, but the same larger blocks may increase conflict misses by packing fewer blocks into each set) — this cross-coupling is discussed below.

Worked example

Consider block-address trace (already in units of cache blocks, so consecutive integers are consecutive blocks):

0, 1, 2, 0, 3, 1, 2, 0, 1, 4, 0, 10,\ 1,\ 2,\ 0,\ 3,\ 1,\ 2,\ 0,\ 1,\ 4,\ 0,\ 1

Let the real cache have capacity C=2C = 2 blocks, block size BB irrelevant here (already in block units), associativity A=1A = 1 (direct-mapped), with 2 sets, so block address xx maps to set x  2x \bmod 2. We compare against:

  • Infinite, fully associative cache (never evicts).
  • Capacity-limited fully associative cache, size 2, LRU.
  • Real direct-mapped cache, size 2 (2 sets, 1 way each), LRU within — trivial since 1 way means no choice.
StepAddrSet (addr mod 2)Infinite: hit/missCap-2 FA: hit/missReal DM: hit/missClassification
100miss (first time)missmissCompulsory
211miss (first time)missmissCompulsory
320miss (first time)miss (evicts LRU=0)miss (set 0 held addr 0, now evicted)Compulsory
400hitmiss (0 was evicted at step 3)miss (set 0 holds 2, not 0)Capacity
531miss (first time)missmissCompulsory
611hitmiss (1 evicted at step 5)miss (set 1 holds 3, not 1)Capacity
720hithit (2 is in cache from step 3/4 refresh)hit? — check: set 0 currently holds addr 0 (from step 4)Conflict (Cap-FA hits, real misses)
800hithitmiss (set 0 now holds 2 from step 7)Conflict
911hithit (Cap-FA has {1,3}? let's re-check)miss (set 1 holds 3)Conflict
1040miss (first time)missmissCompulsory
1100hitmiss (0 evicted for 4)missCapacity
1211hithitmiss (set 1 holds 3 or was refreshed?)Conflict

Let's verify the Cap-2 FA trace carefully with explicit LRU stacks (most-recent on left): 1. access 0 → miss, stack: [0] 2. access 1 → miss, stack: [1,0] 3. access 2 → miss, evict LRU=0, stack: [2,1] 4. access 0 → miss (0 not present), evict LRU=1, stack: [0,2] 5. access 3 → miss, evict LRU=2, stack: [3,0] 6. access 1 → miss, evict LRU=0, stack: [1,3] 7. access 2 → miss (2 not present!) — correction: this is actually a capacity miss, not conflict, since 2 was evicted at step 6. Let me redo the classification with this corrected Cap-2 FA trace. Recomputing column 4 with the correct LRU stack evolution:

StepAddrCap-2 FA stack after accessCap-2 FA result
10[0]miss
21[1,0]miss
32[2,1] (evict 0)miss
40[0,2] (evict 1)miss
53[3,0] (evict 2)miss
61[1,3] (evict 0)miss
72[2,1] (evict 3)miss
80[0,2] (evict 1)miss
91[1,0] (evict 2)miss
104[4,1] (evict 0)miss
110[0,4] (evict 1)miss
121[1,0] (evict 4)miss

With capacity 2 and a working set that keeps cycling through more than 2 distinct recently-used blocks, every access after the first two is a capacity miss in the FA-2 cache — this trace thrashes even fully associative caches at size 2, which is itself an instructive lesson: a small enough cache turns nearly everything into a capacity miss, leaving nothing left over to blame on conflicts. Final classification (infinite cache hits exactly when the address was seen at any earlier step):

StepAddrInfiniteCap-2 FAClassification (vs Cap-2 FA)
10missmissCompulsory
21missmissCompulsory
32missmissCompulsory
40hitmissCapacity
53missmissCompulsory
61hitmissCapacity
72hitmissCapacity
80hitmissCapacity
91hitmissCapacity
104missmissCompulsory
110hitmissCapacity
121hitmissCapacity

Totals for this trace: Compulsory = 4 (blocks 0,1,2,3,4 are 5 unique blocks but block 4 appears once... actually unique blocks touched are {0,1,2,3,4} = 5, matching 5 compulsory misses: steps 1,2,3,5,10). Correcting the count: Compulsory = 5, Capacity = 7, Conflict = 0 (since Cap-2 FA already misses on everything the real direct-mapped cache misses on — the tiny 2-block cache is so capacity-starved that associativity is irrelevant here). Total misses = 12 out of 12 accesses (every access in the real direct-mapped cache misses, since with only 2 blocks and 5 unique addresses reused densely, nothing survives). This worked trace teaches an important lesson beyond the mechanical classification: when capacity is severely constrained, capacity misses dominate and conflict misses can vanish entirely, because there's no "extra" hardware freedom (full associativity) left to lose in the first place — the fully associative reference cache of the same tiny size is already thrashing just as badly. Conflict misses only show up distinctly when the fully associative cache of the same size would have done meaningfully better than the limited-associativity real cache — which requires a larger, less pathological cache size relative to the working set, as shown in the earlier compact SVG example.

Common pitfalls & traps

PitfallWhy it happensFix
Attributing all misses to "capacity" without isolating conflictMiss rate alone doesn't reveal cause; conflict misses look identical to capacity misses from the outsideSimulate the fully associative reference cache of the same size to isolate the true capacity component
Increasing associativity to fix what is actually a capacity problemAssuming any miss-rate improvement from associativity generalizes; but if conflict misses are near zero, more ways barely helpRun the Three Cs classification first; only add associativity if conflict misses are a large share
Using FIFO or random replacement in the reference cachesBreaks the stack/inclusion property, so miss counts are no longer strictly nested (Bélády's anomaly can occur)Use LRU (or OPT) consistently across all three reference caches
Confusing "larger block size reduces compulsory misses" with "larger blocks always reduce total misses"Larger blocks exploit spatial locality (fewer compulsory misses) but also mean fewer blocks fit in a fixed-size cache, and fewer sets, raising capacity and conflict missesAlways check total miss rate and bytes transferred, not just compulsory-miss count, when changing block size
Ignoring conflict misses caused by problematic strides (e.g., matrix access patterns aligned to power-of-two strides)Direct-mapped or low-associativity caches can suffer massive conflict misses when accesses repeatedly land in the same set, even with plenty of spare capacity elsewhereIncrease associativity, pad array dimensions, or use skewed/hashed indexing to break the alignment

Pioneers & history

The Three Cs framework crystallized from decades of cache-simulation research into memory hierarchy behavior, most influentially formalized in analyses connecting stack-based replacement algorithms (LRU, OPT) to cache-size inclusion properties — work that made rigorous the earlier empirical observation that "some misses would happen no matter what you do, and some misses are purely a hardware placement artifact."

Origins · 1 minRichard Rivest Mattson and how this was discovered
RR
Richard Rivest Mattson
1932–2025 · IBM researcher who co-authored the seminal 1970 paper establishing the stack-algorithm framework (the "Mattson et al." stack-distance theory) underlying why fully associative LRU caches of increasing size have monotonically non-increasing miss counts — the mathematical bedrock the Three Cs model relies on

History
1970
— Mattson, Gecsei, Slutz, and Traiger publish the stack-algorithm and stack-distance theory for memory reference behavior, establishing the inclusion property for LRU and OPT.
1987
— Mark Hill's PhD dissertation and related papers formalize and popularize the "Three Cs" (compulsory, capacity, conflict) terminology for cache miss classification in computer architecture.
1989
— Hill and Smith publish widely cited work using the Three Cs model to evaluate associativity and cache organization tradeoffs across benchmark suites.
1990
s — Trace-driven simulators (e.g., Dinero-style tools) operationalize the Three Cs classification as a standard teaching and research technique.
2000
s–present — The model remains the standard first-pass diagnostic in cache design, extended informally with a "fourth C" (coherence misses) in multiprocessor cache-coherence contexts.

Where this matters

Every general-purpose CPU cache hierarchy — L1, L2, L3 on chips from ARM, x86, and RISC-V implementations — is designed using exactly this reasoning. When architects decide L1 data caches should be small but highly associative (e.g., 32KB, 8-way) while L3 caches are large but lower associativity (e.g., 8MB, 16-way relative to size), they are directly trading off conflict-miss risk against hit-time and area, informed by Three-Cs-style simulation on representative workloads (SPEC benchmarks, real application traces). Compiler and systems engineers exploit the same taxonomy: loop tiling and blocking transformations in numerical linear algebra (BLAS, LAPACK) are explicitly designed to keep working sets under the cache's effective capacity, directly targeting capacity misses. Array padding techniques (adding unused columns to matrices) are a direct, well-known countermeasure against conflict misses caused by power-of-two strides colliding in the same cache sets. Hardware and software prefetchers in modern processors (stream prefetchers, stride prefetchers) exist specifically to hide the latency of compulsory misses by fetching blocks before first use. Translation lookaside buffers (TLBs) and disk/web caches use analogous compulsory/capacity/conflict reasoning even outside the CPU cache context, since the Three Cs model is a general property of any finite, associativity-limited caching structure, not just SRAM caches. This classification is also the conceptual stepping stone toward computing overall Cache Performance and Average Memory Access Time, where miss rate (decomposed by cause) combines with miss penalty and hit time into the single number that predicts real execution time.

Ask the tutor: Given a fixed total cache size, how do I choose the block size and associativity that jointly minimize the sum of compulsory, capacity, and conflict misses for a specific workload?

Why do we need three separate categories instead of just tracking overall miss rate?

Overall miss rate tells you that a cache is underperforming but not why, and different causes demand entirely different fixes. If most misses are capacity misses, adding associativity barely helps — you need more space. If most misses are conflict misses, a bigger cache with the same limited associativity might not help much either, but more ways or better indexing will. If most misses are compulsory, no amount of extra capacity or associativity helps at all — you need larger blocks or prefetching to attack the cold-start cost. Without the breakdown, an architect might spend area budget on the wrong structural change, improving miss rate marginally while leaving the dominant cause untouched. The Three Cs model turns miss-rate tuning from guesswork into a targeted diagnostic process.

Are compulsory misses truly unavoidable, or can software eliminate them?

Compulsory misses are unavoidable for the cache itself — by definition, no cache configuration change (size, associativity, replacement policy) can prevent the first touch to a block that has literally never been loaded before. However, the cost of a compulsory miss can be hidden or reduced. Software or hardware prefetching can issue the fetch before the processor actually needs the data, overlapping the miss latency with useful work, so the miss still technically occurs but no longer stalls the pipeline. Larger block sizes reduce the number of compulsory misses (since a whole neighborhood of addresses is pulled in on one miss) though this trades off against capacity and conflict effects. So compulsory misses are structurally guaranteed, but their performance impact is not.

How does this relate to Bélády's anomaly, and why does it matter for the model's correctness?

Bélády's anomaly is the counterintuitive phenomenon where, under FIFO replacement, increasing cache size can sometimes increase the number of misses on the same trace — a violation of the intuitive expectation that bigger caches always do at least as well. This matters because the Three Cs model's correctness argument relies on the inclusion property: a size-(k+1)(k{+}1) fully associative cache's contents at any point in time must be a superset of a size-kk cache's contents, driven by the same trace. This property holds provably for stack algorithms like LRU and OPT but fails for FIFO, precisely because FIFO can exhibit Bélády's anomaly. If you built the reference caches with FIFO, "capacity misses" computed by subtracting size-kk from size-(k+1)(k{+}1) miss counts could become negative or otherwise nonsensical, so the model implicitly requires LRU or OPT to remain mathematically sound.

Can a cache have zero conflict misses in practice?

Yes — a fully associative cache has zero conflict misses by definition, since there is no restricted placement to cause them; every miss it experiences is either compulsory or capacity. More practically, even a set-associative cache can experience negligible conflict misses on workloads whose access patterns are well-distributed across sets (e.g., sequential array scans with no unfortunate power-of-two stride alignment), so conflict misses in real set-associative caches are highly workload-dependent, not a fixed hardware constant. Conversely, some workloads (matrix operations with power-of-two row strides, certain hash table implementations) can produce pathologically high conflict-miss rates even in caches with substantial associativity, because many distinct, actively-used addresses map to the very same small number of sets.

Does increasing block size always reduce compulsory misses, and is that always good?

Increasing block size does reduce the count of compulsory misses for a program with good spatial locality, because one miss now fetches more contiguous, likely-to-be-used data — this is the direct mechanism by which larger blocks amortize the fixed cost of a cache-line fetch. But it is not an unconditional win. For a fixed total cache capacity, larger blocks mean fewer total blocks (and fewer sets, for fixed associativity), which can increase both capacity misses (less distinct data held at once) and conflict misses (fewer sets means more addresses compete for each one). It also increases miss penalty per miss, since more bytes must be transferred from the next level. The net effect on overall performance is workload-dependent and is exactly why block-size tuning is done empirically alongside Three-Cs classification, not by a single universal rule.

How does this model extend to multiprocessor systems with cache coherence?

In shared-memory multiprocessors, an informal "fourth C" — the coherence miss — is often added to the taxonomy. A coherence miss occurs when a block is invalidated or downgraded in a core's private cache because another core wrote to (or read/wrote) the same block, forcing a re-fetch even though the block would otherwise still be valid under the classic single-core Three Cs reasoning. Coherence misses are not compulsory (the block was seen before locally), not capacity (the local cache had room), and not conflict (no local set collision) — they are a distinct effect caused entirely by inter-core communication and the coherence protocol (e.g., MESI). This extension shows the Three Cs model's core methodology — attribute each miss to its true root cause — generalizes cleanly beyond the single-core case it was originally designed for.

If I only have a miss-rate number from a real chip, can I still estimate the Three Cs breakdown?

Not directly from hardware performance counters alone in most commodity chips, since they typically report only aggregate miss counts, not the ideal fully associative or infinite-cache comparisons. The Three Cs breakdown is fundamentally a simulation-based technique: it requires replaying the exact same memory reference trace through software models of the infinite cache, the capacity-limited fully associative cache, and the real cache, then comparing miss counts stage by stage. Some research tools and specialized hardware performance-counter extensions approximate parts of this (e.g., estimating capacity misses via cache-size "what-if" sampling), but an exact breakdown on live silicon generally requires trace capture followed by offline simulation, which is standard practice in architecture research and performance modeling but not something you read directly off a production CPU's counters.

Before this

Where this leads