2026-09-09
Store-to-load forwarding is the CPU's trick for letting a young load read a value from a still-in-flight store without waiting for it to hit L1. But the forwarding path itself is a shared resource, and it has a bandwidth limit that shows up in real workloads: most modern x86 cores can only complete one successful store-to-load forward per cycle per load port.
Here's why. When a load issues, it broadcasts its address to the store buffer's CAM (content-addressable memory). Every store buffer entry compares its address against the load in parallel. If a match is found, the store buffer must:
The shifter/aligner is expensive silicon — it's basically a byte-level crossbar. Duplicating it for every load port would blow the area budget. So Intel and AMD build one full-width forwarding aligner and let load ports arbitrate for it. If two loads in the same cycle both need forwarding, one wins and the other replays the next cycle.
Concrete example. Consider a hot inner loop doing linked-list traversal where each node's next pointer was just written a few instructions earlier:
mov [rdi+8], rax — store next pointermov rdi, [rdi+8] — load next pointer (forwarded from store buffer)mov [rdi+8], rbx — store into new nodemov rdi, [rdi+8] — load again (forwarded again)Even though every load hits the store buffer with a clean forward, throughput caps at one iteration per cycle — not the two your dual load-port CPU could sustain from L1. VTune shows this as LD_BLOCKS.STORE_FORWARD counter events or as elevated load port occupancy.
Rule of thumb: if your loop does N store-forwarded loads per iteration, your minimum cycles-per-iteration is N, regardless of how many load ports you have. Two load ports only help when at least one load hits L1 directly.
The workaround is usually the same: hoist the store out of the loop when possible, or restructure so consecutive loads target already-committed data in L1 rather than freshly-written data in the store buffer.
