6.1.11 · D5Parallelism & Multicore

Question bank — Vector - SIMD instructions (SSE, AVX, NEON)

1,486 words7 min readBack to topic

This page attacks the ideas behind SIMD, not the arithmetic. Each item below is a one-line question with a ::: reveal. Cover the answer, commit to a guess, then check. If you get one wrong, the misconception it targets is exactly the one that will bite you in real code.

Before we start, three words we will lean on the whole way — earned in plain language so nothing appears unexplained:

Keep those three pictures in mind; every trap below is one of them in disguise. The parent note, Data Paralelism, Loop Vectorization, and Flynn's Taxonomy are the background you are being tested against.


True or false — justify

SIMD gives a speedup because the CPU runs faster during vector instructions
False — the clock speed is identical; the win is that one instruction touches lanes at once, so you issue fewer instructions, not quicker ones.
A 256-bit AVX register always processes 8 elements
False — 8 only for 32-bit floats. The same 256 bits holds 4 doubles (64-bit each) or 32 bytes (8-bit each); element count = width ÷ element size.
If your array has 1000 elements and , SIMD does exactly 125 vector instructions
True — exactly, so no scalar remainder is needed; the danger only appears when is not a multiple of .
SIMD is a form of MIMD in Flynn's Taxonomy
False — it is literally the "SIMD" quadrant: one instruction stream, many data streams. MIMD (multiple instruction, multiple data) is separate cores each running their own program.
Unaligned loads (_mm_loadu_ps) always crash
False — they run correctly, just potentially slower because the data may straddle two cache lines. It is the aligned load _mm_load_ps on an unaligned pointer that faults.
A dot product's w-lane multiply gives you the answer directly
False — multiply produces partial products in separate lanes; you still need a horizontal reduction (8→4→2→1) to sum across lanes into one scalar.
Doubling register width from 128 to 256 bits always doubles real-world throughput
False — theoretical width doubles, but memory bandwidth, unaligned traps, and scalar tails cap real gains near 3×–6×; the formula is an upper bound.
Saturating add and normal add differ only when values overflow
True — for 8-bit lanes, both give the same result until the sum exceeds 255; only then does saturating clamp to 255 while normal wraps to a small number.
SIMD requires all lanes to hold the same value
False — lanes hold different values (that's the "Multiple Data"). They must only undergo the same operation (the "Single Instruction").

Spot the error

float* p = malloc(16); _mm_load_ps(p); — what's unsafe?
malloc does not guarantee 16-byte alignment, so the aligned _mm_load_ps may fault; use aligned_alloc(16, ...) or the unaligned _mm_loadu_ps.
A loop for (i=0; i<n; i+=8) do_avx(a+i); with n=10 — what breaks?
It processes indices 0–7 then jumps to 8 and reads a[8..15], running past the array and also skipping nothing but reading garbage; you need a scalar cleanup loop for the last 2 elements, or n=10 isn't a multiple of 8.
ADDPS used on an array of doubles — what's wrong?
ADDPS is packed single (32-bit floats). On 64-bit doubles you must use ADDPD (packed double), or you'll reinterpret each double's bits as two garbage floats.
Vectorising if (a[i]>0) b[i]++; else b[i]--; with plain SIMD — why does it stall?
SIMD cannot branch per-lane. You must compute both the increment and decrement across all lanes and blend by a comparison mask, doing double the work — often slower than scalar for branchy code.
Summing 8 products with one vaddps and reading lane 0 — what's missing?
vaddps adds two vectors lane-wise, it does not collapse a single vector's 8 lanes into one. You still need horizontal adds / extracts to reduce across lanes before lane 0 holds the total.
vmovaps [c], xmm0 where c is a 17-byte offset into a buffer — the flaw?
17 is not divisible by 16, so this aligned store traps; either realign the buffer or switch to the unaligned vmovups.

Why questions

Why must the number of vector instructions use the ceiling ?
A partial final vector (say 3 leftover elements) still consumes one full vector instruction plus masking, so you round up — you can't issue "0.375 of an instruction."
Why does alignment matter if unaligned loads work anyway?
The CPU fetches memory in 64-byte cache lines; an aligned vector sits inside one line (one fetch), while an unaligned one can span two lines, doubling the memory traffic for that load.
Why is horizontal reduction inherently less efficient than the vertical multiply?
Vertical ops keep data in its own lane (fully parallel), but a horizontal sum must shuffle data across lanes, and each cross-lane step depends on the previous, forming a serial log-length chain that the wide ALU can't parallelise.
Why does SIMD pair naturally with Loop Vectorization and Auto-vectorization but not with recursion?
Vectorization needs a fixed count of independent, uniform iterations to pack into lanes; recursion has data-dependent, variable-depth control flow with no such static, branch-free, parallel structure.
Why is memory bandwidth, not lane count, often the true speedup ceiling?
A wide ALU can consume 8 floats per cycle, but if the memory subsystem only delivers 2 floats per cycle, the extra lanes sit idle — see Cache Optimization for why locality and prefetching matter more than raw width here.
Why does packing data contiguously in memory matter for SIMD?
A single movaps grabs elements from consecutive addresses; if elements are scattered, you must gather them one by one (or use costly gather instructions), destroying the one-load advantage.
Why isn't SIMD the same thing as multicore parallelism?
SIMD widens one instruction inside one core; multicore runs separate instruction streams on separate cores. They are orthogonal — you can (and do) combine both, plus Instruction-Level Parallelism within each lane's pipeline.

Edge cases

What speedup does SIMD give when (single element)?
Essentially none, and possibly a slowdown — you pay load/store setup for a full vector but use only one lane; scalar code wins for tiny .
What happens to when exactly (e.g. 8 elements, )?
You get one perfect vector instruction, so — the ideal case where the ceiling adds no waste.
If (one element over a full vector), what's the hidden cost?
Two vector instructions are issued for elements — the second processes just one useful lane, so efficiency drops to about 50% on that leftover, exactly why remainder handling matters.
What does a saturating 8-bit add do at the boundary ?
It clamps to the maximum 255 instead of wrapping to 14; the "extra" 15 is discarded, which is correct for brightness/colour where values can't exceed max.
Is a zero-length array () safe to feed to a SIMD loop?
Only if the loop guard checks n>0 first; vector instructions is correct, but a naive for(i=0; i<n; i+=w) already skips the body, whereas a fixed unrolled call would read out of bounds.
When all lanes hold identical values, does SIMD still help?
Yes for throughput (still one instruction for results), but it's wasteful of information — a broadcast (vdupq_n/_mm_set1) that fills lanes from one scalar is the right tool, common when adding a constant like brightness to every pixel.
Recall One-line self-test

The single sentence that unlocks every trap above ::: SIMD does the same operation on different data, one instruction wide — so anything that breaks sameness (branches), width (element size), contiguity (alignment/gather), or count (remainders) is where it fails.