The Uop Cache's 3-Way Fetch Limit: Why Only Three Uop Cache Lines Feed the Backend Per Cycle

2026-08-29

The micro-op cache is the fastest instruction source your CPU has — Intel's Decoded Stream Buffer (DSB) delivers up to 6 uops/cycle to the backend on Skylake-family cores, bypassing the legacy decoders entirely. But there's a subtle throughput limit almost nobody knows about: the DSB can only fetch from at most 3 uop cache lines per cycle, and each of those lines can hold up to 6 uops. In practice, you almost never get 3 full lines because of how uops are packed.

Here's the structure. The DSB is organized as 32 sets × 8 ways, and each way (a "line") holds up to 6 uops. A line terminates on: 6 uops filled, a branch, a 32-byte boundary crossing, or certain uop types (like microcoded instructions). When the frontend wants uops for a given 32-byte instruction window, it can pull from up to 3 lines that map to that window — but only 3.

Why this hurts: if your code averages 2 uops per DSB line (common when you have lots of small branches or awkward instruction alignment), the DSB caps you at 6 uops/cycle — same as the frontend budget, no problem. But if you average only 1.5 uops per line, you're stuck at 3 × 1.5 = 4.5 uops/cycle, and the backend starves. This is a real ceiling: no amount of ILP saves you when the frontend can't feed the machine.

Real example: a tight interpreter dispatch loop with an indirect jump every ~8 bytes. Each jump terminates a DSB line early. If the compiler doesn't align dispatch targets, you get lines holding 1–2 uops each. Intel's topdown analysis in perf will show a high Frontend_Bound.DSB stall — the uop cache is technically hitting, but delivering nowhere near 6 uops/cycle. Aligning branch targets to 32-byte boundaries and coalescing dispatch code often recovers 15–25% throughput.

Rule of thumb: divide your hot loop's uop count by the number of DSB lines it occupies. If that ratio drops below ~4, you're leaving frontend bandwidth on the floor. Use perf stat -e idq.dsb_uops,idq.dsb_cycles — dividing gives uops-per-DSB-cycle. Below 4 means the 3-line fetch limit is biting you.

This is one of those cases where the uop cache hits but still underperforms. The cache isn't the bottleneck; the read port from the cache is.

Key Takeaway: The uop cache can deliver 6 uops/cycle in theory, but only from 3 lines at once — poorly-packed lines from branch-heavy code silently cap frontend throughput below the backend's appetite.

All newsletters