The Uop Cache's Multi-Branch-Per-Line Limit: Why Only One Taken Branch Fits Per Decoded Cache Line

2026-09-01

The micro-op cache (Intel calls it the DSB, Decoded Stream Buffer) stores already-decoded uops so the CPU can skip the expensive x86 decode pipeline on hot code. But it has an obscure structural rule that bites real workloads: each uop cache line can hold at most one taken branch, and that branch must be the last uop in the line.

On Skylake-era and later Intel cores, a uop cache line holds up to 6 uops corresponding to a 32-byte window of x86 code. The line terminates early on any of these events:

The taken branch rule is the sneaky one. If your loop body contains a conditional jump that's usually taken, that jump ends the uop cache line — even if only 2 uops were packed into it. The remaining 4 uop slots are wasted. Worse, if you have branch-heavy code (say, a chain of predicted-taken branches every few instructions), you might pack only 1–2 uops per line, blowing through the uop cache's capacity 3× faster than expected.

Concrete example: A tight interpreter dispatch loop with a computed-goto pattern (indirect branch every ~4 instructions). Naively you'd expect the loop to fit comfortably in the ~1500-uop DSB. But because every dispatch is a taken indirect branch, each uop cache line holds only ~3 uops instead of 6. Effective capacity halves, and once you exceed it, execution falls back to the legacy decode path — costing you the classic 4-wide decoder throughput cap versus 6-wide DSB delivery.

Rule of thumb: For hot loops, compute uop cache footprint as:

lines_used ≈ ceil(uops_per_iteration / min(6, uops_between_taken_branches))

If your loop has a taken branch every 2 uops, your effective density is 2 uops/line, so a 30-uop loop consumes 15 cache lines — not 5. At 8 lines per 32-byte region and limited sets, you can thrash the DSB with what looks like tiny code.

Mitigation: Straighten hot paths so the predicted-taken direction is the fall-through (invert branch conditions), unroll loops to amortize the terminating backward branch across more uops, and avoid clustering multiple taken branches within a 32-byte window.

See it in action: Check out Silicon to Token: How the Hardware of Machine Learning Actually Works by Vector Meridian to see this theory applied.
Key Takeaway: A taken branch terminates its uop cache line regardless of how many slots remain — branch-dense code silently halves your decoded-cache density and can push hot loops out of the DSB entirely.

All newsletters