Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • How to Use This Course
  • Reading Paths
  • Concept Map
  • How to Read the Diagrams
  • Part 0: What Is a Protocol?10
  • Part 1: Timing and Clocking11
  • Part 2: Encoding and Framing12
  • Part 3: Handshakes13
  • Part 4: Flow Control17
  • Part 5: Transactions19
  • Part 6: Concurrency and Performance17
  • Part 7: Ordering13
  • Part 8: Sharing and Arbitration12
  • Part 9: Deadlock and Liveness13
  • Part 10: Reliability and Recovery20
  • Part 11: Bring-Up and Link Management10
  • Part 12: Power8
  • Part 13: Shared State and Coherence9
    • Why Coherence
    • Invalidate or Update
    • MSI, MESI and MOESI
    • Snooping or Directory
    • Ownership and Forwarding
    • Coherence and Consistency
    • I/O Coherence and Cache Maintenance
    • Protocols Compared: Shared State and Coherence
    • Drills: Shared State and Coherence
  • Part 14: Security and Isolation7
  • Part 15: Specifying and Verifying11
  • Part 16: Protocols Dissected17
  • Part 17: Capstones5
  • Part 18: Interview Preparation6
Loading lesson

‹Part 13: Shared State and Coherence
  1. Topics
  2. First Principles of Hardware Protocols
  3. Part 13: Shared State and Coherence
  4. Invalidate or Update

Part 13 · Shared State and Coherence · Working (W)

Invalidate or Update

When a cache writes a shared line, it can invalidate every other copy or send each the new value. A model of a producer and three consumers shows where each wins, and why almost every real protocol invalidates.

On this page

    Note

    Level: Working (W) · Answers: What if there are several copies of one value?

    Before this: Why Coherence

    A core updates a shared counter 16 times in a loop while three other cores hold copies of it. One protocol sends the new value to all three after every increment: 16 messages, and the other cores did not look at any of the intermediate values. Another protocol removes the three copies after the first increment and lets the other cores fetch the line again if and when they want it. Which is cheaper depends entirely on the pattern of writes and reads.

    When a shared line is written, should the other copies be removed or updated?

    The obvious answer, and why it breaks

    The obvious answer is to update: the other caches keep valid copies, so their next read hits, and nobody waits for a miss. But every write then costs a message to every sharer, whether or not anyone reads the value before it changes again. Writes tend to come in runs (a loop, a structure being filled), so update protocols often send many values nobody reads.

    The idea: two policies, two costs

    Policy

    On a write to a shared line

    Cost

    Good for

    Invalidate

    Remove every other copy once; later writes are local

    One message per run of writes, then a miss for each reader

    Runs of writes before anyone reads

    Update

    Send the new value to every sharer on every write

    One message per write; readers always hit

    One write, then many reads, repeated

    Build it: a producer and three consumers

    Each round, the producer writes one line K times, then each of three consumers reads it once. The model counts bus transactions per round for both policies (a broadcast counts once).

    example.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    """Invalidate or update: bus transactions for a producer and three consumers.
    
    One cache line. Each round the producer writes it K times, then each of the three
    consumers reads it once. Every bus request counts as one transaction (a broadcast
    invalidation or update reaches all caches in one transaction).
      invalidate: the producer's first write in a round invalidates the sharers; later
                  writes in the round hit its now-exclusive copy. Each consumer then misses
                  and fetches the line.
      update:     every write broadcasts the new value to the sharers; consumers always hit.
    """
    ROUNDS, CONSUMERS = 1000, 3
    
    
    def simulate(protocol, k):
        # state of each consumer's copy: True = valid; producer starts as a sharer too
        valid = [True] * CONSUMERS
        producer_exclusive = False
        bus = 0
        for _ in range(ROUNDS):
            for _ in range(k):                       # producer writes K times
                if protocol == "invalidate":
                    if not producer_exclusive:
                        bus += 1                     # one invalidation broadcast
                        valid = [False] * CONSUMERS
                        producer_exclusive = True
                else:
                    bus += 1                         # one update broadcast per write
            for c in range(CONSUMERS):               # each consumer reads once
                if not valid[c]:
                    bus += 1                         # read miss: fetch the line
                    valid[c] = True
                    producer_exclusive = False       # the producer's copy is shared again
        return bus / ROUNDS
    
    
    ks = [1, 2, 3, 4, 8, 16]
    inv = [simulate("invalidate", k) for k in ks]
    upd = [simulate("update", k) for k in ks]
    print("writes per round:      " + " ".join(f"{k:5d}" for k in ks))
    print("invalidate, bus/round: " + " ".join(f"{v:5.1f}" for v in inv))
    print("update, bus/round:     " + " ".join(f"{v:5.1f}" for v in upd))
    assert inv == [1 + CONSUMERS] * len(ks) and upd == [float(k) for k in ks]
    cross = next(k for k, i, u in zip(ks, inv, upd) if u >= i)
    print(f"PASS: update costs less only while a round has fewer than {cross} writes (equal at {cross}); beyond that invalidation's cost stays flat at {1 + CONSUMERS}")

    It prints:

    Diagram
    writes per round:          1     2     3     4     8    16
    invalidate, bus/round:   4.0   4.0   4.0   4.0   4.0   4.0
    update, bus/round:       1.0   2.0   3.0   4.0   8.0  16.0
    PASS: update costs less only while a round has fewer than 4 writes (equal at 4); beyond that invalidation's cost stays flat at 4
    The model's numbers: invalidation costs 4 transactions per round whatever K is (one invalidation, three misses); update costs K, so it wins only below K = 4.

    Invalidation's cost per round is fixed: one invalidation plus one miss per consumer. Update's cost grows with every write. With one write per round, update is four times cheaper; with sixteen, four times dearer. Real workloads contain plenty of runs of writes, and an invalidation-based protocol also needs no bandwidth for lines that one core uses privately.

    How real protocols chose

    Protocol family

    Policy

    MSI, MESI, MOESI, MESIF (almost all processors)

    Invalidate (fact T23)

    AMBA ACE and CHI

    Invalidation-based (fact A23)

    Some early multiprocessors (the Dragon and Firefly protocols)

    Update, for workloads dominated by producer-consumer sharing

    What breaks

    A design (illustrative) used update coherence for a shared statistics block that one core wrote thousands of times per second and others read once a second. Almost all bus bandwidth went to delivering values nobody read. Switching to invalidation cut that traffic by orders of magnitude; the readers paid one miss per second each.

    Why this matters

    Senior lens. The choice is a bet on the access pattern: invalidate bets that writes come in runs, update bets that each write is read. General-purpose systems invalidate because runs of writes and private data are common. If a specific structure really is write-once, read-by-many, solve it at that structure (a message queue, a push to the consumer) rather than changing the coherence policy for everything.

    Interview

    Asked: "Why do most coherence protocols invalidate rather than update?"

    Weak answer: Invalidating is simpler.

    Strong answer: Because writes tend to come in runs and many values are overwritten before anyone reads them. Invalidation pays once per run, one invalidation and then a miss per reader that actually wants the line, while update pays a message to every sharer on every write. For a producer writing K times and three consumers reading once, invalidation costs four transactions whatever K is, and update costs K, so update only wins for very short runs. It also keeps private data free of traffic.

    Follow-ups: (1) When would update win? (2) What does invalidation cost the reader?

    Check yourself With five consumers, at what K do the two policies cost the same in the model's terms?

    Invalidate costs 1 + 5 = 6 per round, update costs K: they tie at K = 6.

    Check yourself What does update coherence give readers that invalidation does not?

    Their next read hits, with no miss latency, because their copy was kept current.

    Key takeaways

    • On a write to a shared line, invalidate the other copies or update them.

    • Invalidation costs once per run of writes plus a miss per reader; update costs a message per write.

    • Runs of writes are common, so almost every real protocol invalidates.

    Keep going

    Next: Lesson 13.3, MSI, MESI and MOESI. The states each cache line can be in, and what each extra state saves.

    Related topics

    • Part 13 overview: Shared State and Coherence

    Previous

    181 / 234

    Next: MSI, MESI and MOESI

    Up next

    Up nextMSI, MESI and MOESI

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Snooping or Directory
    • Ownership and Forwarding
    • Coherence and Consistency