The Store Buffer: Why Your Writes Aren't Where You Think They Are

2026-05-14

When your CPU executes mov [addr], rax, the store does not immediately reach the cache. It lands in the store buffer — a small FIFO (typically 56 entries on Skylake, 72 on Ice Lake, ~80 on Zen 4) that holds pending writes while the core continues executing later instructions. This is the single biggest reason modern CPUs can sustain multiple writes per cycle despite cache and coherence latencies in the tens of cycles.

The store buffer exists because committing a store to L1 requires acquiring the cache line in Modified state via MESI, which may stall on an RFO (Read-For-Ownership) to another core. Instead of blocking the pipeline, the store retires into the buffer and the core moves on. A background commit unit drains entries to L1 in program order.

Store-to-load forwarding: When a subsequent load hits an address still sitting in the store buffer, the CPU forwards the value directly — no cache access needed. This works only when the load is fully contained within a single store's bytes and aligned correctly. Partial overlap (e.g., a 4-byte store followed by an 8-byte load covering it) triggers a store-forwarding stall, typically 10-15 cycles while the store drains to L1 and the load re-issues.

The visibility problem: Because stores sit in a per-core buffer, other cores cannot see them until they drain. This is precisely why x86 — despite being "strongly ordered" — permits StoreLoad reordering. The classic Dekker's algorithm bug:

Both cores can read 0 because each store is parked in its own store buffer while the load proceeds against stale cache. The fix is MFENCE (or any locked instruction), which drains the store buffer before any further loads.

Rule of thumb: A store-forwarding stall costs roughly the same as an L2 hit (~12 cycles). If you write a uint64_t and then read the low 4 bytes back, you'll usually forward fine. If you write four bytes and read eight, you'll stall. Profile with perf stat -e ld_blocks.store_forward.

Real-world example: Aeron, the high-performance messaging library, pads its ring-buffer producer cursor and aligns claim operations to 8 bytes specifically so the producer's write of the cursor can be forwarded to its own subsequent reads without stalling, while consumers — on other cores — wait for the drain plus an explicit fence on the producer side to guarantee visibility.

See it in action: Check out SETTINGS to change after installing fl studio🔥#shorts by inversion to see this theory applied.
Key Takeaway: Your store isn't in the cache yet — it's in a per-core buffer, which is why x86 reorders StoreLoad and why same-core reads are fast but cross-core reads need fences.

All newsletters