Everything measured in this course so far was measured on one machine: an AMD Ryzen 5 7430U, x86-64, with AVX2 and FMA and no AVX-512 at all — five CPUID leaves, all zero, printed in the artifact's section 0. That is a real limit and the artifact's limits block says so in the same words. But “this machine is x86-64” is not an excuse for the rest of the course being x86-shaped, because a vector loop is the one piece of code that most needs to be portable and is hardest to make so.
$ ./simdbench | sed -n '/7\. THE SAME SHAPE/,/three architectures in the room/p'
7. THE SAME SHAPE ON THREE ARCHITECTURES
QUOTED, NOT MEASURED. This machine is x86-64 and there is no other
architecture in the room. Every claim below is a manual claim with a
document, and the limits block says so a second time.
So this concept is quoted, and the discipline is worth stating before the quotes: every claim below is a manual claim with a document, and not one of them is a measurement. Where this course measured something, it says so and gives the number. Here it does not, and the distinction is the point — the previous five courses each made a point of it, and this one is no different.
Why the concept exists at all is that the three architectures make structurally different choices about the same two questions, and the differences are not degree-of-support differences. They are the difference between a fixed register width and a chosen one, and between having a gather instruction and not having one. A loop written for one of them and benchmarked on another is not a portable benchmark; it is two different benchmarks wearing one source file.
Every vector loop, on every machine, answers the same five questions. The questions are the same. The answers are not.
Rows 3 and 4 are the same on all three architectures, and row 4 is the sentence to take away: N independent lanes, one instruction, with a reduction at the end. That is the portable shape, and it is the same sentence the privilege course and the multiprocessor course each arrived at in their own vocabulary.
Rows 1, 2 and 5 are where a benchmark stops meaning the same thing. Row 1 because the width is a compile-time constant on one architecture and a runtime property on another. Row 2 because a gather exists on x86-64 and does not exist in NEON at all. Row 5 because predication exists on SVE and RISC-V V and does not exist on the other two.
The width is a property of the encoding: VEX encodes 128 or 256 bits, EVEX encodes 512 and adds the mask, the broadcast and the embedded-rounding fields. That is why the ISA course's length concept is the place to go for the register-width question and not here. AVX2 added vpgatherdd and vpgatherqd, and the previous concept measured them and found them slower than four ordinary scalar loads. The mask is k0–k7 with 64 bits each, and it is AVX-512 only — absent on this machine, proved rather than assumed. And the tail has no answer on a non-AVX-512 part: handle n mod 4 scalar, which the artifact measured as free.
This is the structural difference, and it surprises people who have only written x86 SIMD. NEON is 128 bits and there is no wider general-purpose SIMD register in the base architecture at all. A 4-wide double loop is 2-wide, and there is nothing to change that. So the loop this course measured at 3.89× does not exist as a single register on AArch64 — it exists as two 2-wide halves, or as four accumulators, which is the independence result from module 2 arriving early and unasked for.
Two more differences, and both are absences that turn out to be features:
ldr into a D register, so a gather costs what four scalar loads cost. The architecture with no gather instruction is the one where the hand-written gather is not merely better but the only thing there is — which is what the previous concept measured as 2.78× on x86. The portable code is the hand-written one, and on this machine it is the faster one too.ldr q0, [x0] is unaligned-tolerant by default, so the alignment question the previous concept spent a table on — is the aligned form faster, and what happens if you get it wrong — simply does not arise.SVE is the opposite design and it is worth reading as a deliberate answer to the two problems this course measured. The register size is programmable, up to 2048 bits, so a loop written once runs at whatever width the implementation chooses — and the two problems the previous concepts found both have answers here. Predication: a sequence of predicated instructions, so a tail is a predicate rather than a branch or a scalar epilogue. That is the answer to the 1.77× overlapping-iteration row, and it is not a small answer — it removes the cost of the entire problem.
The V extension has named VLEN-bit register groups, currently VLEN ≥ 128, with the width again a property the implementation chooses. Two features are worth naming precisely because they show where AVX-512 was heading. v0 is the mask register — one bit per element, and v0.t4 selects four 32-bit lanes — and it is part of the register file rather than a separate operand. A masked instruction reads vl elements and ignores the rest, so the tail is a change to vl and nothing else: no epilogue, no branch, no scalar code. And a segment load — vlseg — can fetch a strided run into a vector in one instruction, which is the operation the x86 gather is trying to be, done by a different instruction for a regular pattern rather than an arbitrary index list. vrgather.vv exists for the irregular case, for the same reason x86 has one.
Take the loop this course has been measuring — A[i] = A[i]*B[i] + C[i] over 64 doubles, with a reduction at the end — and ask what each architecture can do with it. Read this as a table of decisions, not of features.
And the three observations that matter, in order.
One: the portable loop is the one that is slowest on the machine you develop on. If you write the hand-gathered version — four scalar loads assembled into a vector — it is the only option on NEON, it is 2.78× faster than the hardware gather on this x86 machine, and it is roughly comparable to a vrgather on RVV. The one implementation that is bad everywhere is the one that uses the widest instruction available at the time of writing, because that instruction is the one that does not exist on the next target.
Two: the width is a portability hazard, and the fix is to not depend on it. A loop whose inner trip count is n / 4 is written for a 256-bit register. On NEON that silently becomes n / 8 and leaves half the data unprocessed unless the code generator knows the target width — and if the code generator does not, the bug is a wrong answer, not a crash. Code that says “process four at a time” where four means “one register” is not portable code; it is x86 code with a generic-looking loop bound.
Three: the two portable rows are 3 and 4, and they are enough. Elementwise, independent, one instruction, reduce at the end. That compiles to something reasonable on all three. Everything else in the table — the width, the gather, the tail — is where a portable implementation has to give something up, and the interesting engineering question is which something.
Which is a shorter list than it looks, because all three of those are the same mistake: writing the fast case for the machine you have and calling it portable. The measure of a portable vector loop is not that it compiles on three architectures; it is that it is correct on all three and that you can say out loud, for each, what you gave up.
for (i = 0; i < n; i += 4) is a claim about a register, not about a language.)vpgatherdd, and a segment load for a strided case. (Expect the scalar version to be the only one that exists everywhere, and to be the fastest of the three on the x86 machine you are sitting at. That double win is rare in systems work and it is worth noticing when it happens — the portable choice and the fast choice agreeing is not something to expect by default.)Backwards, this concept is the answer to both questions the last two concepts raised. The boundaries concept measured a gather instruction at 2.78× four scalar loads and a tail at 1.77× for the clever approach; NEON has no gather and therefore no gather problem, and SVE and RISC-V V have predication and therefore no tail problem. The measurements were never about the machine — they were about the shape of the access, and the shape is architecture-independent even when the instruction is not. The width concept is where the register table lives, and its third trap — that a 512-bit register is not four times a 128-bit one in practice — is the upper-ZMM aliasing, which is an x86-specific artefact of a fixed-width design and does not arise on a machine where the width is chosen.
Forward, the harness concept is the ledger, and the thing it has to be honest about is this one: every claim in this concept is quoted and none of it is measured, so the harness's group on the three architectures checks that the artifact says so, and nothing else. That is the correct treatment and it is worth noticing how small that group is next to the group on the working-set sweep.
Outward, this is the concept where the course's chain becomes visible. The ISA course's length concept is where VEX and EVEX are laid out and where the 0x62 prefix that introduces a 512-bit instruction is decoded; this concept says what the instruction is for, which is the half the ISA course deliberately left open. The privilege course's vector-19 exception is the one place the word SIMD appeared in five courses, and it is an exception table — a case where the ISA contract has an escape hatch and the hardware agrees. The WebAssembly course's instruction concept defines a 128-bit vector type for machines that do not have one, and this concept is the honest reading of what that buys: a portable subset of all three row-1 answers, which is the NEON one, because 128 bits is the only width every architecture in this course agrees on.
And one outward-facing consequence, because this is the concept a backend author will return to. The portable width is 128 bits and it is portable because it is the smallest, not because it is good. Every performance result in this course was measured at 256 bits, and every one of them would be different at 128 — the working-set collapse in module 1 would happen at half the size. Choosing a target width is choosing which ceiling you will hit first, and the choice is a property of the machine rather than of the program. That is the last thing this course has to say about width, and it is a better place to say it than in module 1, because by now you know what the ceiling is made of.