A Glimpse of Vectorisation
SIMD registers and packed instructions that process several elements at once.
One Instruction, Many Values
So far every instruction you have read operates on a single value at a time: one add adds one pair of numbers, one imul multiplies one pair. Modern x86-64 CPUs can do better. They have vector (or SIMD, Single Instruction Multiple Data) instructions that operate on several values packed side by side in one wide register, all in a single step.
When the optimizer rewrites a loop to use these instructions, it is called vectorisation or auto-vectorisation. This lesson is a first look: enough to recognise vectorised code when you see it, not a deep dive. It is a fitting end to the chapter, because vectorisation is the optimizer at its most dramatic, several iterations of your loop collapsing into a handful of wide instructions.
The Wide Registers
The general-purpose registers you know (rax, edi, and friends) hold one integer each. Alongside them, the CPU has a separate set of SSE registers named xmm0 through xmm15. Each is 128 bits wide, which is room for four 32-bit ints side by side:
128-bit xmm register
+--------+--------+--------+--------+
| int[0] | int[1] | int[2] | int[3] |
+--------+--------+--------+--------+
A vector instruction operating on an xmm register acts on all four lanes at once. Add two xmm registers and you get four independent integer additions for the price of one instruction. That is the whole idea.
A Loop That Vectorises
Here is a function that triples every element of an eight-element array:
void scale(int* data)
{
for (int i{0}; i < 8; ++i)
{
data[i] = data[i] * 3;
}
}
At -O0, this is an ordinary scalar loop, handling one element per iteration with the now-familiar stack traffic:
scale(int*):
push rbp
mov rbp, rsp
mov QWORD PTR -24[rbp], rdi
mov DWORD PTR -4[rbp], 0
jmp .L2
.L3:
mov eax, DWORD PTR -4[rbp]
cdqe
lea rdx, 0[0+rax*4]
mov rax, QWORD PTR -24[rbp]
add rax, rdx
mov edx, DWORD PTR [rax]
mov eax, DWORD PTR -4[rbp]
cdqe
lea rcx, 0[0+rax*4]
mov rax, QWORD PTR -24[rbp]
add rcx, rax
mov eax, edx
add eax, eax
add eax, edx
mov DWORD PTR [rcx], eax
add DWORD PTR -4[rbp], 1
.L2:
cmp DWORD PTR -4[rbp], 7
jle .L3
nop
nop
pop rbp
ret
This is a single element per pass: compute the address of data[i], load it, triple it (add eax, eax then add eax, edx is x*2 + x), store it back, increment i, loop while i <= 7. Eight trips around for eight elements.
The Vectorised Version
Now -O2:
scale(int*):
movdqu xmm1, XMMWORD PTR [rdi]
movdqa xmm0, xmm1
pslld xmm0, 1
paddd xmm0, xmm1
movdqu xmm1, XMMWORD PTR 16[rdi]
movups XMMWORD PTR [rdi], xmm0
movdqa xmm0, xmm1
pslld xmm0, 1
paddd xmm0, xmm1
movups XMMWORD PTR 16[rdi], xmm0
ret
There is no loop at all. No counter, no cmp, no jump back. The eight elements are processed in just two groups of four, and the registers in play are xmm0 and xmm1, the wide vector registers. Let us decode the new mnemonics:
movdqu xmm1, XMMWORD PTR [rdi]loads four ints at once from the start of the array intoxmm1.XMMWORD PTRmeans a 128-bit (16-byte) access;movdquis "move double-quadword unaligned".pslld xmm0, 1shifts each of the four lanes left by 1, which multiplies each value by 2 (theddmeans it operates on packed doublewords, i.e. 32-bit ints, in parallel).paddd xmm0, xmm1adds the four original values back on.padddis "packed add doubleword": four independent additions in one instruction. Shift-by-one plus the original isx*2 + x, that is,x*3, computed for all four elements simultaneously.movups XMMWORD PTR [rdi], xmm0writes the four results back to memory at once.
Then the same four instructions repeat for elements 4 through 7 (note the 16[rdi] offset: 16 bytes is four 4-byte ints further along). Eight multiplications by 3, done as two packed operations. Your eight-iteration loop became a few straight-line vector instructions.
Recognising Vectorised Code
You do not need to memorise the SIMD instruction set to read vectorised assembly. You just need the tells:
xmmregisters (xmm0..xmm15) instead of the usualeax/rdifamily.XMMWORD PTRmemory operands, signalling 128-bit (16-byte) accesses.- Packed mnemonics, often with a
pprefix (paddd,pmulld,psubd) or operating onxmmregisters (movdqu,movdqa,movups,pslld). The trailingdfrequently means "doubleword", i.e. packed 32-bit lanes.
When you see those, read it as "this is doing several elements per instruction". The exact lane count depends on the element size and register width (four ints in a 128-bit register; wider AVX registers like ymm hold eight).
Why It Is Not Always Possible
Auto-vectorisation is powerful but fragile. The optimizer can only do it when it can prove the iterations are independent and the memory access pattern is regular. A known, fixed trip count (like the 8 here) helps a great deal; pointers that might overlap, data-dependent branches inside the loop, or iterations that depend on each other can all prevent it. That is why a small change to a loop can sometimes make vectorisation appear or vanish, and why, when performance matters, programmers learn to write loops in the simple, regular shapes the vectoriser can handle.
That is also why this lesson is "a glimpse". Vectorisation is a large topic with its own instruction sets (SSE, AVX, AVX-512) and tuning techniques. For the purposes of reading assembly, the goal is simpler: when you spot xmm registers and packed instructions in a -O2 listing, recognise that the optimizer has turned your one-at-a-time loop into a several-at-a-time one. That recognition is the perfect note to end the chapter on, because it captures the whole spirit of the optimizer: same result, far less work.
Key Takeaways
- Vectorisation (SIMD) uses wide registers to operate on several values with a single instruction.
- The
xmm0..xmm15registers are 128 bits wide, holding four 32-bitints side by side. - Packed instructions like
paddd(packed add doubleword) andpslldact on all lanes at once;movdqu/movupsmove 128 bits (XMMWORD PTR) at a time. - In
scale, an eight-iteration loop became two groups of four, withx * 3computed aspslld+padddacross four lanes simultaneously. - Recognise vectorised code by
xmmregisters,XMMWORD PTRoperands, and packed (p-prefixed) mnemonics. - Auto-vectorisation requires independent iterations and regular memory access; many loops cannot be vectorised, which is why writing simple, regular loops helps the optimizer.
Create an account to track your progress and access interactive exercises. Already have one? Sign in.
A Glimpse of Vectorisation - Quiz
Test your understanding of the lesson.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!