AnyLearn
All lessons
Computer Scienceintermediate

Superscalar and Out-of-Order Execution

Modern cores issue several instructions per cycle and execute them in whatever order their inputs become ready, while still appearing to run the program strictly in order. This lesson covers register renaming, the reorder buffer, the scheduler, and why the instruction window exists at all.

Updated · AI-authored, review-gated · how lessons are made

Not signed in: your progress and quiz score won't be saved.
Progress1 / 9

Two independent ideas, usually conflated

A simple pipeline caps out at one instruction per cycle. Modern cores go well past that by combining two mechanisms that are often treated as one thing but are genuinely separate.

Superscalar means width. The core has multiple copies of the fetch, decode, and execution hardware, so several instructions can be handled in the same cycle. Intel's Golden Cove core, per Intel's public description, decodes up to 6 instructions per cycle, up from 4 in the previous design, and has 12 execution ports, up from 10.

Out-of-order means reordering. The core executes an instruction as soon as its inputs are ready, rather than in program order.

Each helps alone. Together they compound, and the reason is straightforward: a wide machine only pays off if it can find enough independent work to fill its width each cycle, and strict program order almost never supplies that.

Full lesson text

All 9 steps on one page, for reading, reference, and search.

Show

1. Two independent ideas, usually conflated

A simple pipeline caps out at one instruction per cycle. Modern cores go well past that by combining two mechanisms that are often treated as one thing but are genuinely separate.

Superscalar means width. The core has multiple copies of the fetch, decode, and execution hardware, so several instructions can be handled in the same cycle. Intel's Golden Cove core, per Intel's public description, decodes up to 6 instructions per cycle, up from 4 in the previous design, and has 12 execution ports, up from 10.

Out-of-order means reordering. The core executes an instruction as soon as its inputs are ready, rather than in program order.

Each helps alone. Together they compound, and the reason is straightforward: a wide machine only pays off if it can find enough independent work to fill its width each cycle, and strict program order almost never supplies that.

2. Why program order wastes a wide machine

Consider a load that misses cache and takes a couple of hundred cycles, followed by work that does not depend on it:

ld   r1, 0(r2)      ; misses, ~200+ cycles
add  r3, r1, r4     ; depends on r1, must wait
mul  r5, r6, r7     ; independent
sub  r8, r9, r10    ; independent

In strict program order the entire machine stops at the add for the full duration of the miss, while a 6-wide core sits idle. The mul and sub need nothing from the load and could have run immediately.

Out-of-order execution lets exactly that happen: the add waits, and the independent instructions proceed past it. The core is looking for instruction-level parallelism, and the practical question is how far ahead it is allowed to look. That distance is the instruction window, and it is why the next few structures exist.

3. False dependencies, and why registers are the problem

Reordering runs into a subtlety. Not every dependency is real.

add  r1, r2, r3     ; writes r1
sub  r4, r1, r5     ; TRUE dependency: needs r1
mul  r1, r6, r7     ; reuses r1 for something unrelated

The second instruction genuinely needs the first one's result. That is a true dependency and no hardware can remove it. But the third instruction merely reuses the name r1. It does not need the old value at all. If it were allowed to write r1 before the sub reads it, the sub would get the wrong value.

This is a false dependency, an artefact of the architecture having a small fixed set of register names. x86-64 exposes 16 general-purpose integer registers, so compilers recycle names constantly, and each reuse creates a false ordering constraint that would otherwise serialise unrelated work.

4. Register renaming dissolves the false ones

The fix is to separate the register names the program uses from the physical storage the core actually has. A core keeps a much larger pool of physical registers and, at rename time, assigns each write a fresh physical register.

program (architectural)        after renaming (physical)
add  r1, r2, r3                add  p31, p12, p13
sub  r4, r1, r5                sub  p32, p31, p15
mul  r1, r6, r7                mul  p33, p16, p17

The two writes to r1 now land in different physical registers, p31 and p33. The false dependency is gone, and the mul can execute whenever it likes, including before the sub. The true dependency, sub on p31, survives renaming intact, exactly as it must.

This is the single most important trick in the whole design. Everything else out-of-order execution does is bookkeeping to make renaming safe in the presence of branches and exceptions.

5. The out-of-order core, in order of travel

The shape that matters: the two ends are in order and only the middle is not. Instructions enter in program order and retire in program order, so the architectural state the programmer can observe only ever advances sequentially. Execution in between happens whenever operands are ready. This is what lets the core reorder aggressively while remaining, from the outside, a machine that runs your program one instruction at a time.

flowchart LR
  A["Fetch, in order"] --> B["Decode to micro-ops, in order"]
  B --> C["Rename and allocate, in order"]
  C --> D["Scheduler: wait for operands"]
  D --> E["Execute, OUT of order"]
  E --> F["Reorder buffer"]
  F --> G["Retire, in order"]

6. The reorder buffer and precise state

If instructions finish out of order, how does the machine handle an exception or a mispredicted branch, where everything after a certain point must be undone?

The reorder buffer, or ROB, is the answer. Every instruction gets a ROB entry at rename, in program order. When it finishes executing, its result is marked ready but is not yet architecturally visible. Instructions retire from the head of the ROB strictly in order, and only at retirement does a result become official.

This gives precise exceptions. If instruction 40 faults, instructions 41 onwards have possibly already executed, but none of them has retired, so their effects can be discarded wholesale. The machine can present a clean architectural state at exactly instruction 40 as though nothing after it had ever started.

The same machinery handles branch mispredictions, which is why the misprediction penalty from the previous lesson is a pipeline refill rather than a correctness problem.

7. How far ahead the core can see

The ROB size sets the instruction window: the maximum number of instructions that can be in flight between issue and retirement. It is the concrete answer to "how far past a stalled instruction can the core keep working?"

Intel's published figures for Golden Cove put the reorder buffer at 512 entries, increased from 352 in the previous Sunny Cove core, with 192 load and 114 store queue entries, up from 128 and 72.

Those numbers are worth holding next to the memory latencies from the next lesson, because they set a hard ceiling. If a cache miss costs on the order of two hundred cycles and the core can hold roughly 512 instructions in flight, then a single miss is survivable, provided there is enough independent work nearby to fill the window. If the code hits a dependent chain of misses, where each load's address comes from the previous load, no window size helps: there is nothing independent to run. That case is pointer chasing, and it is the worst thing you can do to a modern core.

8. Speculation is not just about branches

Branch prediction is the famous case, but a modern core speculates in several ways, all following the same pattern: guess, proceed, verify, undo if wrong.

  • Branch direction and target. Covered in the previous lesson.
  • Memory disambiguation. A load following a store to an unknown address might or might not alias it. Waiting for every prior store address would serialise memory access, so the core predicts that the load does not alias, executes it early, and squashes if it turns out it did.
  • Value and address prediction appear in some designs, predicting a load's address from a stride pattern so the access can begin earlier.

The common structure is worth naming, because it explains a whole class of security research: the core does real work on unverified assumptions and relies on being able to erase the result. Erasing architectural state turned out to be easier than erasing every microarchitectural trace, which is the root of the speculative execution vulnerabilities. That is a large topic and this lesson does not attempt it.

9. Why width stopped growing quickly

If wider is better, why did decode width take roughly fifteen years to move from 4 to 6? Three limits bind.

  • Available parallelism. Typical integer code simply does not contain six independent instructions at every point. Studies of instruction-level parallelism have long found that realistic code, with realistic branch prediction, yields a modest sustained IPC regardless of machine width.
  • Scheduler cost. The wake-up and select logic that matches ready operands to free units grows roughly quadratically with the number of entries it must compare each cycle. It is also on the critical path, so making it bigger tends to lower the clock.
  • Rename and retire bandwidth. Every extra instruction per cycle needs another rename port, another ROB write port, and another retire slot. These are expensive in both area and power.

The result is a design that is wide enough to exploit the parallelism that exists, and no wider. Further gains had to come from somewhere else, which in practice meant more cores, wider vectors, and above all better caches. That is where the next lesson goes.

Check your understanding

The lesson ends with a 5-question quiz. Take it in the player above to see your score.

  1. What problem does register renaming solve?
    • It gives the compiler more architectural registers to allocate
    • It removes false dependencies caused by reusing a register name
    • It allows instructions to retire out of order
    • It reduces the branch misprediction penalty
  2. In an out-of-order core, which stages operate in program order?
    • Only execution
    • None of them, which is what makes it out-of-order
    • Fetch, decode and rename at the front, and retirement at the back
    • Only retirement
  3. What is the primary purpose of the reorder buffer?
    • To store operands that instructions are waiting on
    • To predict which branch direction will be taken
    • To hold physical registers freed by renaming
    • To let instructions retire in program order so exceptions stay precise
  4. Why does a large instruction window fail to hide the latency of pointer chasing?
    • Pointer-chasing loads are always cache hits, so there is nothing to hide
    • The reorder buffer cannot hold load instructions
    • Each load's address depends on the previous load, so there is no independent work to run
    • Pointer chasing triggers memory disambiguation squashes on every access
  5. Which of these is a reason decode width has grown only slowly, from 4 to 6 over many years?
    • Scheduler wake-up and select logic grows roughly quadratically and sits on the critical path
    • Wider decode requires a proportionally deeper pipeline
    • x86 instructions cannot be decoded more than four at a time
    • The reorder buffer cannot exceed 512 entries

Related lessons

Programming
intermediate

Benchmarks That Hold Up, and Knowing When to Stop

A benchmark is an experiment, and most are badly designed enough to produce confident wrong answers. This lesson covers what a measurement must control to mean anything, the ways microbenchmarks lie including code the compiler deletes, how to catch regressions in continuous integration despite noisy machines, and how to recognise the point where optimising stops paying.

7 steps·~11 min
Programming
intermediate

Where Time Actually Goes: The Six Usual Suspects

Slow software is slow for a short list of reasons, and each one has a signature you can recognise before you find the code. This lesson covers the six recurring bottleneck classes, waiting on I/O, chatty queries, allocation pressure, lock contention, memory access patterns and serialisation, with the symptom that identifies each and the fix that actually works.

7 steps·~11 min
Programming
intermediate

How Profilers Work, and How to Read a Flame Graph

A profiler is not a neutral observer: sampling and instrumentation see different things, distort the program in different ways, and answer different questions. This lesson covers how each works, why CPU time and wall-clock time give opposite answers, and how to read a flame graph correctly, including the axis that means nothing and is misread constantly.

7 steps·~11 min
Programming
intermediate

Measure First: The Arithmetic That Decides What to Optimise

Most optimisation effort is spent on code that was never the problem, and the reason is that intuition about where time goes is reliably wrong. This lesson covers why guessing fails, the arithmetic that caps what any optimisation can buy, the difference between latency and throughput, and how to set a target that tells you when to stop.

7 steps·~11 min