The previous concept measured what the alignment rule costs: nothing, until it costs everything. This concept is about the two changes the VEX encoding made, and both of them are about the encoding rather than about the arithmetic — which is the shape of most changes to an instruction set, and the reason the assembly course's VEX concept is a prerequisite here rather than a link.
The first change is that a vector instruction can name three registers instead of two. The second is that a 256-bit operation leaves a 128-bit register whose contents depend on which encoding ran — and that second one is where this course's headline finding is. A legacy SSE instruction preserves a YMM register's upper half. A VEX-128 instruction zeroes it. That is the opposite of the story everybody tells, and the artifact reads it out of seven bit patterns rather than arguing it from a manual.
Two-operand SSE means the destination is also a source, so addps xmm0, xmm1 destroys xmm0. If you needed xmm0 afterwards you had to copy it first, and the copy is an instruction. The three-operand form lets you write vaddps xmm2, xmm0, xmm1 and keep both sources.
$ ./vecdump | sed -n '/^ VEX3 |/,/^ arm 0 over/p'
VEX3 | SSE 2-operand, dest IS a source 2.0105 ticks/op | value 150001.000000
VEX3 | SSE 3-operand through a temp 1.9864 ticks/op | value 150001.000000
VEX3 | SSE 2-operand + an explicit copy 1.9831 ticks/op | value 150001.000000
THE THREE ARMS MUST COMPUTE THE SAME VALUE, and they AGREE:
150001.000000, 150001.000000, 150001.000000. The arms are comparable.
arm 1 over arm 0: 1.00x arm 2 over arm 0: 0.99x
arm 0 over arm 1: 1.01x
All three arms print the same value to six decimals, which is the check that makes the durations comparable, and then the copy is 1.00×. The arm that needs a copy is not slower. The assembly course ran the same experiment on a different body and at a different width and got the same answer, and the two agree because the answer is structural: the copy the three-operand form removes is a register move, and a register move the front end can issue for free is not on the critical path.
So read this as a negative result, and then read the second half of it, because that is where the interest is. What the three-operand form buys is a source-level property — the destination may be a register you still need — and a source-level property is exactly what an encoding is for. No duration can price it. Two instructions that retire in the same cycle cost the same cycle whether one of them was necessary; what changes is whether the program that contains them can be written at all. A latency table cannot see a constraint the source language imposes, and a course that reports “free” without saying free in what sense has taught the wrong half.
The third row is the one place a difference could show up — an explicit copy before a two-operand add, measured with the copy hoisted — and in the recorded run it is 0.99×, which is to say the copy is if anything marginally cheaper. That row is also the one that moves most between runs, which is why the artifact prints it as a ratio rather than a verdict: a difference this small is a difference you cannot price, and the honest way to present an unpriceable difference is to print the number and decline to interpret it.
Here is the experiment. Fill lane 4 of a YMM register with a recognisable pattern, run exactly one instruction, and read lane 4 back. If the upper half is untouched you read your own pattern; if it was zeroed you read zero. Seven rows, each a different instruction:
$ ./vecdump | sed -n '/^ DIRTY |/,/TWO vzeroupper/p'
DIRTY | no instruction (the control) | upper 128 bits 0x3dcccccd PRESERVED
DIRTY | ONE legacy addps 0f 58 | upper 128 bits 0x3dcccccd PRESERVED
DIRTY | ONE VEX-128 vaddps c5 f8 58 | upper 128 bits 0x00000000 ZEROED
DIRTY | ONE legacy movaps 0f 28 | upper 128 bits 0x3dcccccd PRESERVED
DIRTY | vzeroupper c5 f8 77 | upper 128 bits 0x00000000 ZEROED
DIRTY | TWO legacy addps | upper 128 bits 0x3dcccccd PRESERVED
DIRTY | TWO vzeroupper | upper 128 bits 0x00000000 ZEROED
0x3dcccccd is 0.1f, the value that was written into lane 4 before every one of these. Every row that says PRESERVED still has it, and every row that says ZEROED is exactly 0x00000000. The control row is the one that makes the other six mean something: without it, a PRESERVED reading is indistinguishable from a register that was never written.
This inverts the standard account, and the standard account is not wrong so much as confused about which encoding is dangerous. The reason vzeroupper exists is the AVX–SSE transition penalty: a processor that has just executed 256-bit operations has its upper vector lanes in a dirty state, and a following legacy SSE instruction that writes the same physical register pays a penalty to reconcile them. A VEX-128 instruction is already in the new state and pays nothing — which is why the transition penalty applies to legacy encodings specifically and why a compiler that emits VEX everywhere, including for 128-bit work, mostly makes the problem disappear.
Now the timing. Six arms, interleaved, min-of-N, with the legacy encoding forced by writing raw bytes in inline asm:
$ ./vecdump | sed -n '/^ VZU |/,/^ D over A/p'
VZU | A AVX 256 only 1.982 ticks/call
VZU | B LEGACY 0F 58 SSE only 10.192 ticks/call
VZU | C VEX 128 SSE only 10.405 ticks/call
VZU | D AVX then LEGACY SSE, no vzeroupper 7.226 ticks/call
VZU | E AVX, vzeroupper, then LEGACY SSE 11.859 ticks/call
VZU | F VEX 128 then LEGACY SSE (the control) 11.119 ticks/call
D over A 3.65x E over D 1.64x F over A 5.61x B over A 5.14x
Read those as five separate claims, because lumping them is precisely how the draft got this wrong.
0f 58 and a VEX c5 f8 58 doing the same sixteen 128-bit adds differ by a couple of percent. A reader who has been told the legacy encoding is catastrophically slow is wrong about that too.vzeroupper between the two bodies and without one. The instruction is three bytes and retires on a port nothing else is using, and it is still about a third more expensive than not doing it.And the honest caption for all six rows: MECHANISM: INFERRED. There is no counter on this machine that can count vector-port occupancy, and the vendor manual has no number for it either. What is measured is that there is no large difference. Every mechanism named in this section is a story attached to a duration, and the limits block says so rather than leaving it to the reader.
The story everybody tells is that AVX instructions dirty the upper half, and that this is why vzeroupper exists. Half of that is right and the half that is wrong is the half that matters, because the wrong half names the wrong instruction as the dangerous one.
WHICH INSTRUCTION DIRTIES THE UPPER HALF?
legacy 0f 58 addps NO -- the upper 128 bits survive
VEX c5 f8 58 vaddps YES -- zeroed
vzeroupper c5 f8 77 YES -- zeroed, on purpose
The dirtying instruction is the LEGACY one.
Read that as a design statement rather than a curiosity. The VEX encoding was introduced to solve a problem, and one of the problems it solved is precisely that a legacy instruction leaves the upper lanes in a state the hardware has to reconcile later. Every AVX-256 instruction is defined to zero the upper half, precisely so that it never has to be reconciled. That is the whole of the encoding’s second benefit, and it is invisible to anyone who only benchmarks throughput.
And the way to tell that a story is wrong is the way this course has used for every claim: pick an operation whose effect is observable in a register and read the register. Not a duration — a bit pattern. The 0x3dcccccd in the upper lane is the number you write before the experiment and read back after it, and it makes the answer unambiguous in a way no timing table ever could. The assembly course’s VEX concept has the encoding side of this; what is new here is that the claim is now a measurement with a number in it.
cd courses/x86simd/assets/samples && ./build_samples.sh, then read section 3B. (Expect the legacy rows to say PRESERVED and the VEX and vzeroupper rows to say ZEROED, and expect the control row to be the reason the other six are readable at all. On a part that follows the usual story, the first two rows are the surprise and everything else follows from them.)addps with the intrinsic _mm_add_ps and rerun. (Expect every row to say ZEROED, because gcc compiles the intrinsic to a VEX encoding — and expect the “legacy preserves” row to disappear entirely, which is retraction 7. An experiment whose arms are chosen by a compiler is an experiment about the compiler.)vzeroupper at all. Build a mixed AVX/SSE program and look at what gcc emits. (Expect it to emit vzeroupper automatically at function boundaries, and expect it not to emit one in the middle of a loop because the penalty is per-transition. Then hand-write the function boundary case and watch the cost appear — and remember that this is a flag-and-encoding question that the ABI course owns, not a vector question.)Backwards, the previous concept measured the alignment rule and found that VEX did not touch it. This one is the other half of what VEX did and the half that is genuinely new: the three-operand form and the upper-half semantics. The pair is worth reading together, because the shape is one popular claim retracted and one popular claim inverted — which is the honest shape of most of what an encoding does.
Forwards. The upper half is a register that exists only in the new encoding, and the next concept asks the obvious question: what happens when you make it 512 bits and add thirty-two more registers and eight mask registers? On this machine the answer is that nothing can be executed at all, which is a different kind of answer and the reason that concept is the hinge of the course. The three concepts together are the vector story: what the register is, what the encoding changed, and where the claims stop being measurable.
Outward. The dirty-upper-bits rule is the one piece of this course that leaks into portable code, because a library compiled with AVX and a library compiled without can be linked into the same process, and the transition happens at the call boundary whether or not anyone wrote vzeroupper. That is the ABI course's calling convention meeting the vector units, and the reason the vzeroupper question has an answer in the callee rather than in the caller. It is also, not coincidentally, why every x86-64 calling convention has a vector register save area at all.