Reading Assembly

Part 5 · SIMD and the modern ISA

Vector registers

A second, wider register file with its own instructions. The register name tells you the width, and the width tells you the parallelism.

30 mincontrasting casesbeacon training
What this lesson is trying to break (2)
  • SIMD is an exotic feature you have to opt into.
  • Wider vectors are always faster.

You have already seen these registers — every vectorised loop in Part 3 used them. This part names them.

A second register file

Floating point and SIMD live in a separate bank from the general-purpose registers, with their own instructions. On x86-64 the same registers serve both scalar floats and vectors, which is why xmm0 appears in code that does no SIMD at all.

Varies: only the register width

NameBitsISAint32 lanesdouble lanes
xmm0xmm15128SSE, SSE2…42
ymm0ymm15256AVX, AVX284
zmm0zmm31512AVX-512168
v0v31 (ARM64)128NEON42

The name is the width. Seeing ymm in a listing tells you 8-lane int32 parallelism without reading anything else. Note that your M1's NEON is 128-bit, so ARM64 code here is 4 lanes where AVX2 gets 8 — a real difference when comparing benchmarks across machines.

Like rax/eax, these overlap: xmm0 is the low half of ymm0, which is the low quarter of zmm0.

Reading the mnemonics

Vector instruction names look like noise until you know they are systematic.

Floating point — the suffix is packing plus precision:

Suffix Meaning
ss scalar single — one float
sd scalar double — one double
ps packed single — 4/8/16 floats
pd packed double — 2/4/8 doubles

So addss adds one float, addps adds four or eight. Scalar float code is therefore full of xmm registers doing no parallel work at all — which surprises people who assume xmm means SIMD.

Integer — a p prefix for “packed”, and a final letter for element width:

paddb bytes · paddw 16-bit words · paddd 32-bit dwords · paddq 64-bit qwords

And a v prefix means the AVX encoding, which is three-operand: vpaddd ymm0, ymm1, ymm2 is ymm0 = ymm1 + ymm2, non-destructive. The older paddd xmm0, xmm1 is xmm0 += xmm1.

Beaconsee this → think this, without decoding it

vpaddd ymm0, ymm1, ymm2 · paddd xmm0, xmm1 · add v0.4s, v1.4s, v2.4s

Vectorised integer addition. Read the width off the register name and the element size off the suffix.

The ARM64 form is the most self-describing of the three: v1.4s means “four 32-bit lanes”, stated in the operand.

When you see any of these in a loop, the compiler autovectorised — so the loop was free of the obstacles in 3.4.

The horizontal problem

SIMD is good at doing the same thing to many lanes, and bad at combining lanes.

Adding two vectors is one instruction. Adding the four lanes within one vector — a horizontal operation — takes a cascade: shift the vector, add, shift again, add. You saw this in 0.2 as psrldq/paddd pairs at a loop’s exit.

That asymmetry shapes how vectorised code is written: keep partial results in lanes for as long as possible and combine once at the end. A loop that horizontally sums every iteration gets no benefit at all. It is also why a reduction (summing an array) is harder to vectorise than a map (scaling an array), and why the tail of a vectorised reduction always looks messy.

Wider is not automatically better

Three reasons, all real:

Not enough data. Sixteen lanes need at least sixteen elements to be worth the prologue and epilogue. Short arrays lose.

Tail handling grows. With 16 lanes you can have 15 leftover elements needing scalar treatment.

Frequency throttling. On several Intel generations, sustained heavy AVX-512 lowers the core clock — so the wide code runs fewer cycles per second. It can be a net loss, and this genuinely surprised people when AVX-512 shipped.

And most commonly: memory bandwidth binds first. If your loop is streaming through memory, you are limited by how fast data arrives (4.4), not by how many lanes you could process. Doubling vector width does nothing when you are waiting for DRAM.

Explain it to yourselfwrite it — thinking it does not count

You see xmm0 used throughout a function that does floating-point maths on single values, with no arrays anywhere.

Explain why that is expected rather than surprising. Then say which specific part of the instruction names would tell you whether any real SIMD is happening.

What you should now be able to say

  • Vectors live in a separate register file, shared with scalar float on x86.
  • The name is the width: xmm 128, ymm 256, zmm 512, NEON v 128.
  • Suffixes are systematic: ss/sd scalar, ps/pd packed; paddb/w/d/q by element size; v prefix means three-operand AVX.
  • Scalar float uses xmm, so xmm alone does not mean SIMD.
  • Horizontal operations are expensive — keep results in lanes, combine once.
  • Wider is not always better: data volume, tails, frequency, and memory bandwidth all interfere.

Next: 5.2 — Why autovectorisation usually fails.

In the wildgo read the real thing now, while it is fresh
  • Intel Intrinsics GuideSearchable by instruction. When you meet an unfamiliar vector mnemonic in a listing, this tells you exactly what it does. Look up PADDD and then PSHUFB to see the range.
Scheduled for recall3 items added to your review queue

These are not shown to you now on purpose. They will surface in Review tomorrow, mixed in among items from other parts of the course — not grouped by topic. Sorting out which idea applies is the part that transfers.