2026-07-01
When you write a value to memory and immediately read it back, the CPU faces a problem: the store hasn't committed to the L1 cache yet — it's sitting in the store buffer. A naïve implementation would stall the load until the store retired. Instead, modern CPUs implement store-to-load forwarding (STLF): the load-store unit snoops the store buffer and hands the load its data directly, bypassing the cache entirely. On Intel, this saves ~4–5 cycles per dependent load.
The catch: forwarding only works when the store and load fully overlap and are aligned. If the load reads bytes the store didn't write — even partially — the CPU falls back to a store forwarding stall: the load waits for the store to drain to L1 (typically 10–20 cycles), then re-issues. The rule of thumb on Skylake and later: the load's address range must be entirely contained within one prior store's address range, and the load must not straddle a cache line. A 4-byte load from a prior 8-byte store at the same address forwards fine. A 4-byte load starting one byte into a prior 4-byte store does not.
Real-world example — the classic union bug:
uint64_t to a union, then read the low uint32_t back. Forwards cleanly — the load is fully contained.uint32_ts to adjacent slots, then read them as one uint64_t. The load overlaps two stores, so forwarding fails and you eat a stall.This is why serializers that do byte-wise writes followed by word-wise reads (or vice versa) tank throughput. It's also why memcpy implementations avoid mixing store widths near a subsequent read of the destination.
Quick calculation: A store forwarding stall costs roughly the same as an L1 miss to L2 (~12 cycles). In a hot loop running 1 billion iterations, one avoidable stall per iteration = ~4 seconds of wasted CPU time on a 3 GHz core. Aligning your stores to match subsequent load widths is one of the highest-ROI micro-optimizations.
How to detect it: On Intel, use perf stat -e ld_blocks.store_forward. Any nonzero count in a tight loop is worth investigating. On AMD, look at ls_stlf events.
The mitigation is usually structural, not local: either widen your stores to match the largest subsequent read, or separate the write-then-read pair by enough independent work that the store drains before the load issues. Compilers don't fix this for you because they can't prove the aliasing in most cases.
