The SIMD course measured that a wider vector is not automatically faster, and it did it with a body the compiler chose. This concept is the part that makes a vector body unsafe to choose: the difference between an instruction that works on any address and one that dies on most of them is a single letter in the mnemonic, and the letter is the only thing standing between a working program and a SIGSEGV that depends on where an allocator happened to put an array.
There is exactly one measurement in this course that observes that requirement directly, and it is not a duration. It is ten forked children, one instruction each, at an address eight bytes off a cache line, and the output is a signal number per row. That choice is the concept: alignment is a correctness requirement and a duration table is the wrong instrument for a correctness requirement, because the difference between a working load and a faulting one is not a slowdown you can amortise away. It is a program that does not run.
The table is PRINTED AND NOT MEASURED, which is a real distinction here and not a formality: the fault half of every row is measured in the next section, one forked child per row, and the encoding half is quoted from the SDM and then checked byte-for-byte in the last concept.
$ ./vecdump | sed -n '/1B1\./,/THE NAMING IS NOT/p'
MOVE | op | pfx | ALIGNED | what the ALIGNED form faults on
MOVAPS| 28 | none| 16 byte | #GP if the address is not 16-aligned
MOVUPS| 10 | none| none | nothing; the unaligned form
MOVDQA| 6F | 66 | 16 byte | #GP; the integer twin of MOVAPS
MOVDQU| 6F | F3 | none | nothing
MOVSS | 11 | F3 | 4 byte | #GP if not 4-aligned
MOVSD | 11 | F2 | 8 byte | #GP if not 8-aligned
MOVLPD| 13 | 66 | 8 byte | #GP if not 8-aligned
LDDQU | F0 | F2 | none | a LOAD that explicitly never faults
The naming is not decorative and it is the fastest way to remember the table. aps is aligned packed single, apd is the double, and dqa is double quadword aligned — the integer twin of movaps with the same requirement and a 66 operand-size prefix in front of it. Wherever an a appears, the instruction faults on a misaligned address. Wherever a u appears, it does not. That is the entire rule, and it holds for all eight rows above.
Three rows in that table are not about 16 bytes and are the ones people forget:
MOVSS and MOVSD require only 4 and 8 bytes, because they move one scalar and the scalar's own alignment is the requirement. MOVSS is opcode 11 and MOVSD is opcode 11 too — the difference is a prefix, F3 against F2, which is a reminder that a mnemonic is not an opcode.MOVLPD requires only 8 even though it moves a packed double, because the second half of the destination is not a memory operand at all.LDDQU requires nothing, ever. It is the only instruction in the table whose description says “never faults”, and it exists because the unaligned load was too useful to lose while movups was a slightly worse version of it. The difference is the hint: movups tells the hardware the data is unaligned and lets it skip a check that is already known, and on a machine that could load across a page boundary that hint is load-bearing.And the prefix column is where the trap in the ABI course's last concept reappears in a new costume. 66 is not a direction flag and not a size flag in the general case — in the legacy encoding space 66 is an operand-size override, and for a mov it turns a 32-bit operation into a 128-bit one. That is why movdqa is 66 0f 6f and movdqu is f3 0f 6f, and why movdqa and movaps are the same instruction wearing a prefix and a name.
The buffer is mmap'd, so the compiler knows nothing about its contents and cannot fold a load into a constant. Each probe is one instruction in its own forked child, so a fault is a signal number rather than a dead artifact.
$ ./vecdump | sed -n '/^ ALIGN |/,/vmovdqa load | aligned/p'
ALIGN | vmovapd load | aligned: returned | +8 bytes: SIGSEGV | legacy encoding
ALIGN | vmovupd load | aligned: returned | +8 bytes: returned | legacy encoding
ALIGN | vmovapd store | aligned: returned | +8 bytes: SIGSEGV | legacy encoding
ALIGN | vmovupd store | aligned: returned | +8 bytes: returned | legacy encoding
ALIGN | vmovdqa load | aligned: returned | +8 bytes: SIGSEGV | legacy encoding
ALIGN | vmovdqu load | aligned: returned | +8 bytes: returned | legacy encoding
ALIGN | movaps load | aligned: returned | +8 bytes: SIGSEGV | legacy encoding
ALIGN | movups load | aligned: returned | +8 bytes: returned | legacy encoding
ALIGN | vmovaps load | aligned: returned | +8 bytes: SIGSEGV | VEX encoding
ALIGN | vmovdqa load | aligned: returned | +8 bytes: SIGSEGV | VEX encoding
THE RESULT, and it is not a timing table at all:
Every ALIGNED form faults at +8 bytes and every unaligned
form returns -- LEGACY AND VEX ALIKE.
Read the last two rows before reading anything else on this page. vmovaps and the VEX form of vmovdqafault exactly like their legacy twins, and this course was written asserting that they would not. That claim is retraction 15, and it is the commonest false statement about AVX: that the VEX prefix removed the alignment requirement. It did not.
And the SDM says the thing the measurement found, in as many words: the memory operand “must be aligned on a 16-byte (128-bit version), 32-byte (VEX.256) or 64-byte (EVEX.512) boundary or a #GP will be generated.” The #GP is the interesting half, because it becomes a SIGSEGV in a user process and looks identical to an out-of-bounds access. A misaligned movaps is indistinguishable, from the kernel's point of view, from a wild pointer — and that is a real cost of the aligned form that has nothing to do with speed.
One row deserves its own note. vmovdqa load appears twice in that table — once with a legacy prefix and once with a VEX one — and they are two probes that share a spelling. A harness that counted names would collapse them to one and conclude the table had nine rows. The partition this concept asserts is over probes, and the harness checks that the two sets of five and four sum to ten without any row appearing in both.
Here is the thing that makes this rule cost real money. A misaligned movaps raises #GP, the kernel turns that into a SIGSEGV, and the resulting core dump points at a wild pointer. So the failure mode of a vectorised loop over a correctly-sized buffer looks exactly like the failure mode of a buffer overrun — and the two have completely different fixes.
There is a second trap, and it is the one that cost this course a retraction. The obvious way to write the probe is in C:
$ gcc -O2 -mavx2 -S probe.c | grep -A2 'vmov\|movap'
vmovaps (%rax),%ymm0 # the compiler chose the ALIGNED form
You asked for a load and got a faulting load, and on a machine where the buffer happens to be 16-byte aligned the experiment passes and teaches you nothing. Worse, the “m” constraint on a double[4] makes gcc emit a safe sequence instead, so the instruction you meant to test is not in the binary at all — and a probe that was never executed reports “returned” just as cleanly as one that was. That is retraction 11, and the build script here answers it by disassembling every probe body and letting the harness assert the mnemonics are present. A probe that returns must have run, and the only way to know is to look at the bytes.
The general form is worth carrying: when you are measuring a property of one instruction, write that instruction yourself and then prove it is the one that ran. Everything else in this course is downstream of that habit.
cd courses/x86simd/assets/samples && ./build_samples.sh, then read section 2. (Expect six rows to fault and four to return, and expect the two VEX rows to be the surprise. On a machine whose SDM section differs, the vmovaps row is the one that tells you your expectation was wrong rather than the machine.)*(volatile __m128 *)p and let the compiler pick the instruction, then disassemble the body. (Expect vmovaps or vmovdqa, and expect the “m” constraint on a double[4] to make gcc emit a safe sequence instead — which is retraction 7 in the SIMD course's compiler concept and the reason the probe bodies here are hand-written asm. A probe that silently uses a different instruction than the one you meant to test is the commonest failure in this whole collection.)movss and movsd for the packed forms. (Expect them to return at +2 bytes and fault at +4 relative to an 8-byte boundary, and then work out what a compiler does when it has a 4-byte-aligned struct and a 16-byte-aligned local. The alignment of a struct member is a language question, and the answer propagates straight into a fault.)memalign the buffer and watch the assertion become dead code that the optimizer removes — and notice that this is a real fix and also a way of making the compiler stop telling you about the bug.)movups against movaps on data that is already 16-byte aligned. (Expect almost no difference, and expect a difference on hardware where the unaligned form crosses a cache line — which is why the aligned form exists and why “the aligned form is faster” is true on some machines and not on yours. The requirement is unconditional; the cost is not.)Backwards, the SIMD course's width concept measured that doubling the width did not double the throughput, and its boundary concept is where a tail is handled. This page is the safety half of the same territory, and it deliberately does not repeat the width experiment. The SMP course owns what happens when two cores touch the same aligned line at once; the last three concepts here are that, for x86-64 specifically.
Forwards. The #GP that a misaligned aligned-form load raises is the same fault the third course's inventory counted as si_code 128, so the mechanism is one you have already met as a number. What this page adds is that a fault can be produced by a correctly bounded access to a correctly sized object, which is a category the address-space model alone does not have. The next concept is what the encoding did to the same registers, and its first finding is that VEX did not touch this rule at all.
Outward. The aligned/unaligned split is the single most common place where a portable program stops being portable, and it is worth noticing who picked the requirement: the a forms were added in 1999 for machines that could not load across a cache line cheaply, and every instruction since has inherited the letter. The compiler concept in the SIMD course is where a language decides which of these eight rows your program actually executes, and in every case the answer was “not the one you wrote.”