The Micro-Op Cache's Branch Boundary Restriction: Why a Taken Branch Ends a Uop Cache Line

2026-08-24

The uop cache (Intel calls it the Decoded Stream Buffer, or DSB) stores pre-decoded micro-ops so the CPU can skip the expensive x86 decode path. But it doesn't store uops as a flat stream — it stores them in uop cache lines that map to fixed 32-byte or 64-byte windows of the original instruction bytes. And those lines carry a brutal restriction: a taken branch terminates the line. Whatever slots are left in that line go unused.

On Skylake through Golden Cove, the uop cache is organized as 32 sets × 8 ways, with each way holding up to 6 uops covering a single 32-byte code region. The rules that shape a line:

This means dense branch code — the kind you get in interpreters, state machines, or virtual dispatch — packs terribly. Every taken branch forces the current line to close, even if only 2 of the 6 slots are filled. You end up storing 30–40% fewer uops than the cache theoretically holds, and the front-end starves 4-wide dispatch because the DSB can only deliver 6 uops per cycle from one line.

Concrete example: A bytecode interpreter's dispatch loop looks like load opcode → table lookup → indirect jump. That's ~3 uops per handler tail, and the indirect jump is always taken. Each handler's tail sits in its own uop cache line with 3 empty slots. Now you're consuming 2x the DSB capacity per handler. When the interpreter's hot working set grows past ~1500 uops (out of ~1536 physical), handlers start evicting each other and the front-end falls back to the legacy decoders — which do 4–5 uops/cycle instead of 6, and pay full x86 decode latency on cold paths. You'll see the idq.dsb_uops counter collapse and idq.mite_uops spike.

Rule of thumb: If your hot loop has a taken branch every ~4 instructions, assume the DSB stores only ~60% of the uops its size suggests. Straight-line code with rare taken branches (like memcpy or a math kernel) hits near 100% DSB utilization; branchy dispatch code hits closer to 50%. Every taken branch inside a 32-byte window costs you the tail of a cache line.

Key Takeaway: The uop cache stores uops in lines tied to 32-byte code windows, and any taken branch forcibly ends the line — so branchy code fills the DSB with half-empty lines and starves the front-end long before you hit the cache's nominal capacity.

All newsletters