Cache coherence · Performance Lab

Cache Coherence and MESI

A core cannot simply write to its cache and move on — first it has to make sure every other core's copy of that line is gone. That rule, and the four states used to teach it, is cache coherence.

Learning objective

Explain why a write to a cache line requires exclusive ownership of that line, distinguish cache coherence from memory consistency and memory ordering, and follow a line's state transitions across two cores through read misses, write misses, read-for-ownership, invalidation, cache-to-cache transfer, write-back, and eviction — while clearly separating this conceptual MESI teaching model from what any specific real processor implements.

Prerequisites

  • A basic idea of what a CPU cache and a cache line are. No prior knowledge of coherence protocols is assumed — this lab builds it from scratch. (If you've done the False sharing lab, this explains the mechanism beneath the symptom shown there — but that lab is not required first.)

Theory

Coherence vs. consistency vs. memory ordering

Cache coherence answers: for a single memory location, do all cores eventually agree on its value, and is there a well-defined order of writes to that one location that every core observes consistently? Coherence is a per-location guarantee, enforced by hardware protocols like MESI.

Memory consistency (the memory model) answers a much broader question: across multiple different memory locations, what orderings between operations is a program allowed to observe? Two coherent caches can still permit surprising reorderings across different addresses unless the consistency model forbids it.

Memory ordering (fences, volatile, Ordering::Acquire/Release, happens-before) is the language- and hardware-level mechanism used to constrain what a consistency model otherwise allows.

Keep them apart: coherence guarantees a single AtomicLong never goes backwards or forks into two values on different cores. It says nothing about whether a write to a different variable becomes visible before or after that one — that's a consistency and memory-ordering question, and it's out of scope for this lab.

The MESI states

MESI names four states a cache line can be in, from any one core's point of view:

StateMeaningOther cores hold a copy?Dirty (differs from memory)?
ModifiedSole copy, written since fetched.NoYes
ExclusiveSole copy, not yet written — matches memory.NoNo
SharedClean, read-only copy.PossiblyNo
InvalidStale or absent — must be re-fetched.

The state that makes MESI more than a toy: Exclusive. A line fetched by a read, when no other core holds any copy, becomes Exclusive rather than Shared — so a later write to that same line by the same core silently upgrades Exclusive → Modified with no bus traffic at all, because no other cache needs to be told anything. Without an Exclusive state, that write would issue an invalidation broadcast even though nothing else was sharing the line.

Transitions the interactive model demonstrates

  • Read miss, no sharers — fetches from memory, takes the line Exclusive.
  • Read miss, another core holds it clean — a cache-to-cache transfer supplies the data; both end up Shared, downgrading a lone Exclusive holder.
  • Read miss, another core holds it Modified — the dirty holder transfers its value and writes it back to memory (which was stale); both end up Shared.
  • Write miss (read-for-ownership) — every other copy must be invalidated before the requester becomes sole Modified owner; a Modified holder is transferred and written back first.
  • Write hit, Exclusive → Modified — silent local upgrade, no bus transaction.
  • Eviction — a Modified line must be written back first; an Exclusive or Shared line is simply dropped.

Caveats: a teaching model, not a universal contract

MESIF (some Intel processors) adds a Forward state so only one Shared holder answers a snoop. MOESI (some AMD processors) adds an Owned state, letting a Modified line be shared without an immediate write-back. Directory-based coherence replaces broadcast snooping with a directory tracking sharers, used because broadcast snooping stops scaling past a modest core/socket count — common on large multi-socket servers. ARM and Apple Silicon: ARM's architecture reference does not mandate one specific coherence protocol, and Apple has not published the exact protocol its silicon uses — this lab does not claim to model either.

Conceptual model

Conceptual MESI-style model. Real processors may implement MESIF, MOESI, directory-based coherence, proprietary protocols, different replacement policies, and undocumented microarchitectural behaviour.

Try it

Interactive MESI model

Conceptual model

A simplified four-state view of MESI (Modified / Exclusive / Shared / Invalid) for one conceptual memory line and two CPUs, not a cycle-accurate coherence simulation. See "Caveats" above for what this leaves out.

CPU 0 reads. No other cache holds the line, so it fetches from memory and takes it Exclusive — a clean copy nobody else needs to be told about. Try CPU 0: read.

CPU 0

Invalid

Value: —

CPU 1

Invalid

Value: —

Scenario
Single reader
Step
0 of 0
Memory value
0
Owner
none
Invalidations
0
Transfers
0
Write-backs
0

Event log

  1. Initial state — both lines Invalid.

Java and Rust

Shared-writer example

public class SharedWriterExample {
    // Two threads both write this counter, from different cores — the JVM
    // guarantees atomicity and visibility, but says nothing about, and does
    // not let you observe, which coherence protocol state the line is in.
    private final AtomicLong counter = new AtomicLong();

    public void incrementFrom(int iterations) {
        for (int i = 0; i < iterations; i++) counter.incrementAndGet();
    }

    public long value() { return counter.get(); }
}

Two threads calling incrementFrom concurrently on the same instance produce exactly the coherence traffic the interactive model calls "competing writers": every increment from one thread invalidates the other's cached copy of the line. AtomicLong guarantees the arithmetic is correct regardless — this example demonstrates the coherence cost of shared mutable state, not a correctness bug.

Single-writer ownership example

public class SingleOwnerExample {
    // No synchronization needed: exactly one thread ever reads or writes
    // this field for the object's whole lifetime. In coherence terms, that
    // thread's core can hold this line Exclusive/Modified indefinitely and
    // never has to respond to another core's invalidation.
    private long total;

    public void addFrom(int iterations) {
        for (int i = 0; i < iterations; i++) total += i;
    }

    public long total() { return total; }
}

Not synchronized, and correct anyway — because only one thread ever touches total. This is the software-level analogue of the model's Exclusive/Modified single-owner path: no other core contends for the line, so there is no invalidation traffic to pay for. Contrast with SharedWriterExample, where two threads writing the same field is what produces cross-core invalidation.

These examples do not, and cannot, directly control which MESI state a line is in. The JVM and JIT give no API for observing or forcing coherence states — perf c2c (see "Diagnostic methodology" below) is how you observe the resulting traffic on Linux, not the source code.

The runnable Maven project is at content/labs/mesi/code/java/ in this site's repository.

Diagnostic methodology: perf c2c

Methodology, not a benchmark

This section describes how to investigate coherence traffic on supported Linux systems — it deliberately does not present a synthetic "MESI benchmark" as a portable performance number. Cross-core coherence cost depends heavily on topology, core placement, and microarchitecture; a single measured ratio here would invite exactly the kind of universal claim this lab argues against.

perf c2c (Linux perf's cache-to-cache module) samples hardware performance-monitoring-unit events tagged with the physical cache line address involved in a load, and groups them by which cores took cross-core "HITM" (hit-modified) responses — i.e. cache-to-cache transfers of a dirty line, exactly the transition the "competing writers" and "reader then writer" scenarios above model. It requires:

  • A Linux kernel and CPU with the relevant PMU events available (`perf c2c record`/`perf c2c report`) — this is Linux-specific tooling; there is no equivalent invocation on macOS.
  • Sufficient privileges to use hardware performance counters (commonly CAP_PERFMON/CAP_SYS_ADMIN, or a permissive /proc/sys/kernel/perf_event_paranoid).
  • A workload that actually contends — running perf c2c against a single-threaded program produces nothing interesting to report, by construction.
# Record cache-to-cache events for a running workload
perf c2c record -- ./your-contended-workload

# Summarize which cache lines saw the most HITM (cache-to-cache) traffic
perf c2c report --stdio

Caveats: exact event availability and column meaning vary by microarchitecture and kernel version; virtualized environments frequently restrict or disable the underlying PMU events entirely; and perf c2c's output identifies contended cache lines and the cores/threads touching them, not MESI state names — the mapping from "HITM" back to "this was a Modified→Shared transition" is an interpretation this lab teaches, not something the tool states directly.

Common mistakes

  • Treating MESI as the coherence protocol every CPU implements — it is a teaching model; real hardware ships MESIF, MOESI, or directory-based coherence.
  • Assuming coherence gives you memory ordering for free — coherence guarantees agreement on one location's value, not the order in which writes to different locations become visible.
  • Assuming Apple Silicon, or any specific chip, is "just MESI" — vendors rarely publish their exact protocol.
  • Confusing invalidation with write-back — invalidation discards a stale copy; write-back flushes a dirty value to memory. A single transition can require both, or just one.
  • Reading perf c2c output as if it named MESI states directly — it reports contended cache lines and cross-core HITM events, which this lab's theory maps to MESI transitions, but the tool itself does not use MESI terminology.

When this matters

Worth reasoning about coherence when

  • Multiple threads on different cores write to the same or nearby memory — understanding ownership transfer explains why throughput drops even without a lock.
  • Diagnosing a multi-threaded slowdown with no visible lock contention and no data race — cross-core invalidation traffic is often the missing explanation, and perf c2c is the tool to confirm it.
  • Designing a concurrent data structure where read-mostly vs. write-heavy access patterns should be laid out differently to minimize ownership transfers.

Low payoff from reasoning about coherence when

  • The workload is single-threaded, or threads never touch the same or nearby memory — there is no cross-core coherence traffic to reason about.
  • The bottleneck is already known to be I/O-, network-, or lock-bound — profile before assuming coherence traffic is the dominant cost.
  • You need the exact coherence protocol a specific chip implements — this lab's model is a teaching simplification, not a substitute for that vendor's own documentation (where it exists).

Investigation task

Using the Java or Rust project in code/, and a Linux machine with perf available:

  1. Run two threads concurrently calling the shared-writer example's increment method on the same instance, and separately run the single-owner example on one thread only.
  2. Record perf c2c record/perf c2c report output for the shared-writer run and look for HITM (cache-to-cache, dirty-line) events on the counter's address.
  3. Re-run perf c2c against the single-owner example and confirm it reports little or no HITM traffic for that field — there is only one core ever touching it.
  4. Change the shared-writer benchmark to pin both threads to the same physical core (if your OS exposes CPU affinity) instead of different cores, and predict — then confirm — whether HITM events for that line disappear even though both threads still write it concurrently.
  5. Write down your CPU model, core topology, and what you observed, and explain any difference from the mechanism described in "Theory" above.

Accessibility notes

  • The interactive model is fully keyboard operable: the scenario selector and the Java/Rust code tabs are roving-tabindex tablists (arrow keys, Home/End); every read/write/evict/step/reset control is a native <button>.
  • Every state change is announced to screen readers via an aria-live="polite" region, in addition to the visible state badges and the state inspector table — state is never colour-only.
  • The coherence-bus pulse is the only animation on this page; it plays once per action and is fully collapsed under prefers-reduced-motion: reduce (this page has no auto-playing or looping animation to separately gate on that preference).
  • With JavaScript disabled, this page's theory, Java and Rust code, diagnostic methodology, trade-offs and sources remain fully readable — only the interactive model and the graded quiz require script.

Review questions

Self-check before or after the graded quiz below:

  1. Why does a write to an Exclusive line cost no bus traffic, while a write to a Shared line does?
  2. Give a concrete example of something cache coherence guarantees, and something it does not (that memory ordering separately provides).
  3. Why does reading a line held Modified by another core force both a write-back and a transfer, rather than just one or the other?
  4. Name one thing a real processor's coherence protocol might do differently from this lab's four-state model.

Quiz

Sources