2026-09-09
Store-to-load forwarding (STLF) is the CPU's escape hatch for reading a value before the store that produced it has reached the cache. When a younger load hits an older store still sitting in the store buffer, the CPU forwards the store's data directly. It's a win — but it's not free. Even the fast path of forwarding costs measurable cycles, and the slow paths are brutal.
On modern Intel (Skylake through Golden Cove) and AMD Zen 3+, a successful, fully-aligned store-to-load forward takes ~5 cycles, versus ~4 cycles for a plain L1 load hit. That extra cycle is the CAM (content-addressable memory) lookup: the load queue asks the store buffer, "does anyone have my address?" Every entry in the store buffer compares in parallel, then the youngest matching store wins.
Things get expensive when alignment breaks:
Concrete example: a common trap is casting through a union or writing a struct field-by-field then reading the whole struct:
struct { uint32_t a; uint32_t b; } s; s.a = x; s.b = y; uint64_t both = *(uint64_t*)&s;
The two 4-byte stores can't forward to the 8-byte load. The load stalls waiting for both stores to drain — typically 10–15 cycles instead of 5. In a tight loop that's a 2–3x slowdown.
Rule of thumb: a load can forward from at most one older store, and only if that store's byte range fully contains the load's byte range with matching alignment. Anything else pays the drain penalty.
Performance counters make this visible: on Intel, LD_BLOCKS.STORE_FORWARD counts forwarding failures. If you see this event firing in a hot loop, you're almost always looking at type punning, unaligned struct writes, or a memcpy/memset immediately followed by reads of the same region. The fix is usually to write the wider type first, or to insert enough distance (a few dozen cycles of unrelated work) for the stores to drain naturally.
