Skip to content

Copy-on-Write

Memory-sharing method — instantiates Demand-Triggered Deferred Evaluation

Lets many holders share one physical copy of some data and defers the actual copy until a holder first writes, so unmodified sharing costs nothing.

Copy-on-Write (COW) defers a duplication rather than a computation. Several logical owners are handed what looks like an independent copy of some data, but they all share one physical instance marked read-only; the real copy is made only at the moment an owner first writes, and only for the part being written. Its distinguishing trigger is the mutation itself: a write is the demand signal that activates realization, so read-only sharing — however wide — stays free, and you pay the copy cost precisely where and when divergence actually happens. That makes COW the write-triggered member of this family, as opposed to the read-triggered load of demand paging; the "work" it saves is the copy that would otherwise be made eagerly for owners who never modify anything.

Example

A Unix process calls fork(). Semantically the child gets its own full copy of the parent's address space — potentially gigabytes. Copying all of it eagerly would be enormous waste, because the very next thing many children do is exec() a new program and discard that memory entirely.

Under copy-on-write, fork() instead marks every shared page read-only in both processes and copies nothing. Both run against the same physical pages until one writes: that write traps as a protection fault, the kernel copies just that one page, gives the writer its private version, and lets execution resume. A child that forks and immediately exec()s never triggers a single copy. A child that mutates a few pages copies a few pages. The original stays valid for every reader as long as any reader still holds it, and duplication cost tracks actual divergence rather than nominal ownership.

How it works

  • Share read-only. All holders point at one instance; the shared region is marked so that a write cannot proceed silently.
  • Trap the first write. A write to shared data faults; the fault is the activation rule that fires the deferred copy — allocate a private copy for the writer, redirect it, resume.
  • Preserve the shared original. The unmodified instance remains valid for every other holder; its lifetime extends until the last sharer releases it (typically reference-counted).
  • Copy at the chosen granularity. Only the written unit — a page, a block, a node — is duplicated; everything untouched stays shared.

Tuning parameters

  • Copy granularity — page, block, or whole object. Finer units copy less per write but multiply bookkeeping and faults; coarser units copy more but amortize overhead.
  • Sharing breadth — how many holders share one instance. Wider sharing saves more memory up front but raises the stakes of a write storm.
  • Fault vs. check — hardware write-protection faults vs. an explicit "am I shared?" test before each write. Faults are transparent but costly per event; checks are cheaper per write but must be woven into every mutator.
  • Reference-counting cost — how sharing and release are tracked. Tighter tracking reclaims sooner but adds per-operation overhead and, naïvely, contention.

When it helps, and when it misleads

COW is a clear win when copies are frequently made but rarely modified: process creation, filesystem and volume snapshots, persistent data structures, "copy" of a large buffer that is usually read and discarded. It converts eager O(size) duplication into lazy O(bytes-actually-changed).

It misleads under write-heavy sharing, where its economics invert. If most shared units are eventually written, every first write pays a fault plus the copy — strictly more than an eager copy would have cost — and a burst of writes across widely shared data becomes a fault storm and an allocation spike at the worst moment. In COW storage this also shows up as write amplification: a small logical change forces a whole block to be copied.[1] The classic misuse is treating a COW "copy" as if isolation were free and cheap forever, then being surprised by a latency cliff the first time the workload turns write-mostly. The discipline: reserve COW for read-dominated sharing, size the copy granularity to the real write pattern, and budget for the worst case where every shared unit diverges at once.

How it implements the components

  • activation_rule — the first write to shared data is the trigger that fires the deferred copy; until that write, realization does not happen.
  • input_lifetime_and_capture_rule — the shared original is captured read-only and kept valid for every holder until the last reference is released, so deferral never invalidates a live reader.

It does NOT decide *whether the data is needed (need_predicate — that's the Conditional Workflow Gate), cache computed results (memoized_result_store — that's Memoization), or load absent data on a read fault (demand_signal — that read-triggered path is Demand Paging); COW defers a copy, and its trigger is a write.*

  • Instantiates: Demand-Triggered Deferred Evaluation — COW defers duplication until a write makes a private copy genuinely necessary.
  • Sibling mechanisms: Demand Paging · Late-Materialization Query Plan · Lazy-Initialization Proxy · Thunk or Lazy Promise · Memoization

Notes

Copy-on-write and Demand Paging are close kin — both exploit the hardware fault as a demand signal — but they defer opposite things and trigger on opposite accesses: demand paging loads absent data on a read, COW duplicates shared data on a write. A single mapped region can use both at once, which is exactly why keeping the two mechanisms distinct matters when reasoning about a fault.

References

[1] Write amplification is the ratio of physical bytes written to logical bytes changed. Copy-on-write storage incurs it because a small update forces a full block (or extent) to be copied and rewritten elsewhere; it is the storage-layer face of COW's "cheap until you write" trade-off.