Reading from a cache is easy: if the data is there, hand it over. Writing is where the interesting decisions live. The moment the CPU stores a value, the cache's copy and main memory's copy disagree — and the architect must decide when, and whether, to reconcile them. Those decisions are the write policies, and they trade memory-bandwidth against simplicity and, eventually, against the correctness of a whole multicore machine.
There are really two independent questions. On a write that hits: do we push the new value all the way to memory now, or later? On a write that misses: do we bother pulling the block into the cache at all? Two questions, two-by-two — four combinations, of which two pairings dominate real hardware.
On a write hit, the two schools are:
The dirty bit is the whole trick of write-back: one bit per line that remembers "this block has been modified since it was loaded". On eviction, a clean block (dirty bit 0) can be silently dropped — memory already has it — while a dirty block must be written back first. If a block is written a hundred times then evicted once, write-through does a hundred memory writes; write-back does one. Temporal locality of writes is exactly what makes write-back a huge win.
On a write miss — you are storing to a block that is not in the cache — there is a second choice:
| On a write miss… | What happens | Usually paired with |
|---|---|---|
| Write-allocate | fetch the block into the cache, then write it (bet: more writes/reads to it are coming) | write-back |
| No-write-allocate | write straight to memory, leave the cache untouched | write-through |
The natural pairings follow the philosophy. Write-back + write-allocate keeps everything hot data local and minimises memory traffic — the default in nearly every modern L1/L2. Write-through + no-write-allocate keeps things simple and is handy where writes are rarely re-read soon (some streaming or I/O paths). The reasoning: if you are going to keep the block's only fresh copy locally (write-back), you had better allocate it on a write miss too; if you are pushing everything to memory anyway (write-through), why disturb the cache for a write you may never revisit?
Write-through's flaw is obvious — a store to slow memory on every write would stall the pipeline. The fix is a write buffer: a small FIFO queue between cache and memory. The store lands in the buffer in one cycle and the CPU races on; the buffer drains to memory in the background. Write-back machines use one too, so an eviction's write-back does not block the incoming block. A write buffer can even satisfy a later read to an address still queued in it (write-to-read forwarding), and lets writes be coalesced. It turns the memory-traffic cost of writing from a stall into (usually) free background work — until the buffer fills, at which point the CPU finally must wait.
On a single core, write-back is a clear win and the story ends. Add a second core with its own
cache and write-back becomes a minefield: if core A holds the only fresh (dirty) copy of a block and core B
reads that address, B must somehow get A's value, not memory's stale one. This is the
cache-coherence problem, and the dirty bit you met here grows into the "Modified" state of
the
Simplicity and safety, in the right place. Write-through means memory (or the next level) is always current, so a crash, a DMA device, or another core reading memory directly never sees stale data — no write-back bookkeeping, no "is this line dirty?" question on eviction. Some GPUs, small embedded caches, and level-1 data caches that back onto an inclusive L2 use write-through precisely because a fast write buffer hides its one real cost (traffic) while keeping the design dead simple and the next level's copy trustworthy. As so often, the "worse" policy wins wherever its weakness is cheap and its simplicity is valuable.
Two independent bits ride on every line and beginners conflate them. Valid answers "does this line hold real data at all?" (cleared at reset). Dirty answers "has this valid line been written since it was loaded, so memory is now stale?" A line can be valid-and-clean (safe to drop), valid-and-dirty (must be written back before eviction), or invalid (ignore it entirely). A dirty bit on an invalid line is meaningless. And note: only write-back caches even have dirty bits — write-through keeps memory current, so there is never anything to write back.