5.4.6 · D3Memory Hierarchy & Caches

Worked examples — Write-through vs write-back

3,031 words14 min readBack to topic

This page is the "throw everything at it" workbench for the parent topic. We are going to list every kind of situation a write policy can face, then solve one concrete problem for each kind. Nothing here assumes you already memorised the formulas — each symbol is re-earned the moment it appears.

Before we start, three plain-language reminders so no symbol is naked:


The scenario matrix

Every write problem is a combination of a few independent switches. Here is the full grid — each later example is tagged with the cell (e.g. C3) it covers.

Cell Policy Access outcome Line state What makes it special
C1 Write-through write hit baseline: pay memory every time
C2 Write-through many writes, buffered write buffer hides latency until it fills
C3 Write-back write hit, clean→dirty clean then dirty the "1 ns" happy path
C4 Write-back many writes, same line stays dirty temporal locality → amortisation
C5 Write-back write miss, evict clean line victim clean [[5.4.08-Write-Allocate-vs-No-Write-Allocate
C6 Write-back write miss, evict dirty line victim dirty double memory hit (writeback + fetch)
C7 Degenerate write-once stream, no reuse dirty once limiting case: write-back loses its edge
C8 Both full head-to-head count mixed real-world word problem
C9 Multi-core twist one core dirty, another reads dirty elsewhere [[5.4.07-Cache-Coherence

The nine cells cover: both policies, hit and miss, clean and dirty victims, the buffered case, the zero-reuse limit, a real workload, and the multi-core edge.

One more policy word appears in cells C5–C7, so let us earn it now:

Now we solve each cell.


C1 — Write-through, single write hit

  1. Update the cache line: Cache[0x100] ← 99. Why this step? A write-hit means the line is present, so the cache copy must change first. Cost .
  2. Forward the same store to memory immediately: Mem[0x100] ← 99. Why this step? That is the definition of write-through — memory is never allowed to fall behind. Cost .
  3. Add them because the CPU waits for both to finish:

Verify: the memory term () dwarfs the cache term (), so — matches the parent note's "memory dominates" claim. Units: ns + ns = ns. ✓


C2 — Write-through with a write buffer

The write buffer is a tiny queue (depth ) that catches stores so the CPU need not wait for memory.

  1. The buffer can only empty as fast as memory accepts drains: one slot leaves every . So the sustained drain rate is Why this step? Long-term throughput is set by the slowest stage — here the single memory port — not by how many slots the buffer has.
  2. What does depth buy, then? It absorbs a temporary burst: if writes briefly outrun the drain, up to can queue before the buffer is full. But once those spare slots are used, the CPU can go no faster than the drain can empty them. So the sustained stall condition is Why this step? changes how long you may exceed the drain rate, not the drain rate itself — a deeper buffer just postpones the first stall.
  3. Convert to writes per second so the number is human-readable, and to confirm the threshold ties to a real CPU issue rate: Why this step? CPU store rates are quoted in writes/second; converting lets us directly compare the stall threshold against a chip's actual sustained store rate. Any workload issuing more than M writes/s to this buffer will eventually stall.

Verify: a faster CPU (bigger ) crosses the threshold sooner → stalls sooner. Sanity check the limit: as (instant memory) the threshold , i.e. no stall is possible — correct. Note never appears in the sustained threshold, exactly as expected. ✓


C3 — Write-back, single write hit (clean → dirty)

  1. Update the cache line: Cache[0x100] ← 99. Cost .
  2. Set the sticky note: Dirty ← 1. Why this step? Memory still holds 42. The dirty bit is the only record that a future writeback is owed. Setting it costs no extra time (it flips alongside the write).
  3. Done. No memory access. .

The did not vanish — it was deferred to eviction time. Compare to C1:

Verify: memory 0x100 still equals 42 right after step 2 — exactly the "inconsistent but faster" state the policy promises. ✓


C4 — Write-back, many writes to the same line (amortisation)

This is the cell where write-back earns its keep — temporal locality.

  1. Each of the 1000 writes is a hit → . Why this step? Every write hits an already-present, already-dirty line, so no write ever touches memory.
  2. On eviction, one memory write flushes the final value: . Why this step? Memory only needs the last value, not all 1000 — that is the amortisation.
  3. Total: .
  4. Write-through would pay memory on every write: .
  5. Speedup:

Verify: matches the parent note's "" numerical example. Memory traffic: 1 write (WB) vs 1000 writes (WT) → less bus traffic. ✓


C5 — Write-back, write miss, victim line is clean

Now the address is not in the cache. Under write-allocate, we must first pull the line in.

  1. The slot's current occupant is clean → just discard it, no writeback owed. Cost . Why this step? Clean means cache and memory already agree; overwriting loses nothing.
  2. Fetch the 0x200 line from memory into the cache: . Why this step? Write-allocate says "bring the line in, then write it" (so future writes to 0x200 become cheap hits).
  3. Now write into the freshly-loaded line and set dirty: , Dirty ← 1. Why this step? The store instruction still has to actually deposit the value 7; fetching the line only made room, it did not perform the write. And we mark the line dirty because this new value 7 now differs from what memory holds — otherwise a later eviction would silently lose it.
  4. Total: .

Verify: exactly one memory access (the fetch), because the victim was clean. This is the cheaper of the two miss cases — contrast with C6 next. ✓


C6 — Write-back, write miss, victim line is dirty

The worst common case: the line we must evict still owes memory a writeback.

  1. Write back the dirty victim to memory first: . Why this step? The dirty bit says memory is stale for that old address; we must not lose the newer data.
  2. Fetch the 0x200 line from memory: . Why this step? Write-allocate requires the target line to be resident before we may write into it; the slot is now empty (we just flushed the victim), so we pull 0x200 in.
  3. Write into it, set dirty: . Why this step? Same reason as C5 step 3 — the actual value 7 still has to land in the line, and marking it dirty records that this new value now differs from memory so a future eviction flushes it.
  4. Total:

Verify: two memory accesses = plus the tiny cache write — matches the parent's "" eviction cost. It is worse than C5, exactly one extra . ✓


C7 — Degenerate case: write-once stream, no reuse

The limiting scenario where write-back's amortisation collapses.

  1. Write-back per write = full C6 cost (writeback victim + fetch + cache write) = each. Why this step? No reuse means the amortisation of C4 never happens — every write pays a full miss.
  2. Total WB .
  3. Write-through here uses write-no-allocate (defined in the matrix above): on a miss the write goes straight to memory, the cache is not loaded and no cache line is written. So each write costs just one memory access: Why this step? With no reuse there is nothing to gain by allocating a cache line, so write-no-allocate skips both the fetch and the deposit — the value is simply pushed to DRAM. (Had we instead used write-through with allocate, we would add a fetch and a cache write, giving — but no-allocate is the sensible pairing for a streaming write pattern.)
  4. Total WT .
  5. Ratio:

Verify: write-back is now slower — the exact opposite of C4. This confirms the parent's Mistake 1: "write-through is not always slower." The crossover is caused by zero temporal locality. ✓


C8 — Real-world word problem: full head-to-head

  1. Write-through memory writes = every store: Why this step? By definition WT touches memory on each store.
  2. Write-back memory writes = only dirty evictions: Why this step? A memory write happens only when a line is both evicted (miss rate) and dirty.
  3. Traffic ratio: Write-back uses 40× less memory-write bandwidth.
  4. Average write latency (write-back, write-allocate), using :

Verify: sits between the best case ( ns, C3) and a full dirty miss ( ns, C6) — plausible because misses are rare (). The ratio matches the parent's bus-utilisation derivation exactly. ✓


C9 — Multi-core twist: dirty data seen by another core

The exam trap where write-back's deferred update meets a second CPU.

  1. If B naively reads main memory it gets 42stale. That is the bug write-through would have avoided (memory always current). Why this step? Write-back left the truth only in A's cache.
  2. A coherence protocol must intervene: B's read snoops, A sees it owns the dirty line, and A supplies 99 via a cache-to-cache transfer (and typically writes back to memory). Why this step? Correctness requires the newest value; only A has it.
  3. Cost intuition: write-back adds coherence machinery (snoops, ownership states) that write-through partly sidesteps because memory is authoritative.

Verify: the value delivered is 99, never 42. This confirms the parent table's row "Multi-core Coherence: harder for write-back." Consistency of ordering across cores is formalised by memory consistency models. ✓


Recall Self-test: name the cell before you compute

A dirty line is evicted on a write miss under write-allocate — total cost? ::: (cell C6). Write-through with buffer depth and drain — sustained stall threshold rate? ::: (cell C2); only sets how long a burst is tolerated. When does write-back lose to write-through? ::: Write-once / no temporal locality (cell C7), where every write is a full miss. WB vs WT memory-write ratio with and ? ::: (cell C8).