Part 4 · Where the fictions break
µops: the ISA is an interface
Fiction four, and the last one. What executes is not what you read — which settles the CISC-versus-RISC argument you may still hear people having.
What this lesson is trying to break (3)
- The CPU executes x86 instructions.
- CISC is slower than RISC because the instructions are complicated.
- Two instructions always cost more than one.
Assembly is the machine’s native language. The CPU fetches an x86 instruction and the hardware performs it. That is what “machine code” means.
No modern x86 core executes x86 instructions. A decoder translates each one into one or more internal operations called micro-ops, and the µops are what get renamed, scheduled, executed and retired. The instruction you read is an input to a translator.
The ISA is an interface — a stable public API over a private, changing implementation. Reading assembly tells you what was requested through that API. It does not tell you what the hardware did, and instruction count is not even the right unit.
The counting breaks in both directions
One instruction can be several µops.
add rax, [rbx] is one instruction and at least two µops: a load, then an add.
div is many. A single instruction can occupy the machine for dozens of cycles.
Several instructions can be one µop.
This is macro-op fusion, and it is less well known. The decoder recognises certain adjacent pairs — most importantly a compare followed by the branch that reads its flags — and fuses them into a single µop.
cmp eax, edx
jne .L2
Two instructions in the listing. One operation in the machine.
Which means the loop condition at the bottom of every loop you have read in this
course is cheaper than it looks, and it explains why compilers place cmp
immediately before its branch rather than scheduling other work between them:
separating them would break the fusion.
; A — x86, one instruction
add eax, [rbx]
; B — ARM64, two instructions
ldr w1, [x2]
add w0, w0, w1Same computation. A is one instruction, B is two. Which is faster?
Effectively the same, and that is the entire point.
A decodes into approximately the same two operations that B states explicitly: a load, then an add. x86 hides the load inside the instruction; ARM64 requires you to spell it out. The work is identical and so is the time.
So the instruction count differs by 2× while the actual work differs by 0%.
This is why comparing ISAs by instruction count is meaningless, and why the ARM64 listings in this course being longer never meant they were slower. x86 is a load-store-fused architecture with variable-length instructions; ARM64 is a strict load-store architecture with fixed 32-bit instructions. Different encoding philosophies, same underlying operations.
There is one genuine difference, and it is about the decoder rather than execution: x86 instructions are 1–15 bytes and must be decoded serially to find where the next one starts, which makes wide decoding hard and expensive. ARM64 instructions are all 4 bytes, so the position of the tenth instruction is arithmetic. This is a real advantage — it is why Apple can decode 8 instructions per cycle relatively cheaply — and it is about finding instructions, not running them.
What this settles
The CISC-versus-RISC argument, if you still hear it, is largely over, and the reason is this lesson: x86 chips are RISC-like machines with a CISC front end. The execution core is a pile of simple operations being scheduled; the complexity lives in a decoder.
So the honest 2026 summary, useful if the topic comes up:
- The execution cores of high-end x86 and ARM64 designs are more alike than different: wide, out-of-order, speculative, with similar cache structures.
- The real remaining differences are decoder cost and power (variable versus fixed length), the memory model — ARM64’s is weaker, which matters for lock-free code — and vector width, where AVX-512 is 512-bit and NEON is 128.
- “CISC is slow” has not been true for about twenty-five years.
Where to get real numbers
Since fiction 4 means you cannot count instructions, and fiction 3 means you cannot count cycles from a table either:
llvm-mca— µop counts, port pressure, critical path for a block. Models the core, ignores memory.uops.info— measured µop counts, latency, throughput and ports per instruction per microarchitecture.- A profiler — the only one that measures your program on your data. Which is 6.2.
Someone claims their ARM64 build is more efficient because the same function compiles to fewer instructions than the x86-64 build.
Write the correction. You need two separate points: one about what an instruction is across the two ISAs, and one about what would actually have to be measured.
Part 4 checkpoint
All four fictions are now broken. Out loud:
- Why does out-of-order execution not break your program? (4.1)
- What sets the floor on a loop’s speed? (4.1)
- Latency versus throughput — when does each bind? (4.2)
- Why is division uniquely bad? (4.2)
- What does a predicted branch cost? A mispredicted one? (4.3)
- Why can sorting an array speed up a loop? (4.3)
- Why does pointer chasing lose so badly to array scanning? (4.4)
- What does the CPU actually execute? (4.5)
If those are fluent, you have the thing this course exists to teach: you can read a listing and say sensible things about what it will cost.
Next: 5.1 — Vector registers.