Exercises — Vector - SIMD instructions (SSE, AVX, NEON)
This page is a self-testing ladder. Each problem states a clean question; the full worked
solution hides inside a collapsible [!recall]- callout so you can try first, then reveal.
The rungs climb from L1 Recognition (can you name it?) up to L5 Mastery (can you design
and defend it?). Everything here builds on the parent SIMD note
and touches Loop Vectorization, Data Paralelism, and Auto-vectorization.
Before we start, we re-earn every symbol used throughout so nobody is lost from line one.
The figure below makes , , and the trays concrete. Look at it before reading the formula: the coloured cells are the elements ; the horizontal brackets group them into trays of slots. Notice the third tray is only half full (2 of 4 slots) — yet it is still a whole tray. That single picture is why the ceiling appears in every formula on this page.
Level 1 — Recognition
L1.1 — Name the register
Q. An x86 CPU has a 256-bit register that holds 8 single-precision (32-bit) floats. Which SIMD instruction-set family introduced it, and what is the register called?
Recall Solution
- floats — matches the description.
- The 256-bit registers are YMM registers, introduced by AVX (Advanced Vector Extensions).
- (128-bit XMM = SSE; 512-bit ZMM = AVX-512; 128-bit Q = NEON on ARM.)
Answer: AVX, register YMM.
L1.2 — Lane count from bit widths
Q. How many 8-bit values fit in a 128-bit NEON Q register?
Recall Solution
Answer: 16 lanes.
L1.3 — Same-op rule
Q. True or false: a single SIMD instruction can add the first pair of lanes while multiplying the second pair.
Recall Solution
False. SIMD = Single Instruction, Multiple Data. All lanes execute the same operation in the same step. Different ops per lane needs different instructions (or masking).
Level 2 — Application
L2.1 — Ideal speedup, large array
Q. You process floats with AVX (). Ignoring overhead, what is the speedup ?
Recall Solution
(divides evenly). . Answer: — the ideal because is a clean multiple of .
L2.2 — Number of vector instructions with a remainder
Q. You process floats with SSE (). How many SSE instructions run, and how many scalar cleanup steps remain?
Recall Solution
- Full trays: SSE instructions cover elements.
- Remainder: scalar steps. Answer: 25 SSE instructions, 0 cleanup. (100 is divisible by 4, so the tail is empty.)
L2.3 — Realistic time with a ragged length
Q. elements, , each step costs cycles. Compute , , and (round to two decimals).
Recall Solution
- cycles.
- Trays: . cycles.
- . Answer: , , . Why not exactly 8? The 4th tray is half-empty (only 6 of 8 lanes used) yet costs a full step.
The figure below is exactly this scenario drawn out. Look at the fourth tray: its blue slots are the 6 real elements, the grey slots are the 2 wasted lanes. Those grey slots consume a full cycles yet do zero useful work — that is the visible reason instead of the ideal . Every ragged-length problem on this page is a variation of this picture.
Level 3 — Analysis
L3.1 — Where did the speedup go?
Q. A dot product of 8 floats does 8 multiplies + 7 adds = 15 scalar ops. The AVX version does 1 multiply + 4 reduction ops = 5 ops. The ideal speedup for is 8×, yet measured speedup is only ×. Explain why the horizontal reduction destroys most of the ideal speedup.
Recall Solution
- The element-wise multiply is perfectly parallel: 8 lanes, 1 instruction — that part is 8×.
- The horizontal sum (adding across lanes) is not data-parallel: lane 0 must be added to lane 4, then to lane 2, etc. Vector hardware sums down columns cheaply but across a row only in a tree of steps.
- For : reduction takes pairwise stages (plus the 128-bit extract), i.e. ~4 ops that are essentially serial overhead riding on top of the one fast multiply.
- So the cheap parallel part (1 op) is dwarfed by the fixed reduction cost (4 ops): . Answer: Cross-lane reduction is logarithmic serial work that does not shrink with ; it caps the speedup far below . Lesson: SIMD shines when the output is also a vector, not a single scalar sum.
L3.2 — Aligned vs unaligned bandwidth
Q. Cache lines are 64 bytes. An aligned 32-byte AVX load starting at address 64 touches how many cache lines? An unaligned load starting at address 48 touches how many? What is the bandwidth ratio?
Recall Solution
- Aligned load: bytes lie entirely inside cache line → 1 cache line.
- Unaligned load: bytes straddle line and line → 2 cache lines.
- Ratio : the unaligned load pulls twice the memory traffic. Answer: 1 line vs 2 lines; 2× bandwidth cost for the misaligned access.
L3.3 — Break-even array size
Q. A vectorized loop has fixed setup cost of 12 cycles (loading constants, aligning). Each AVX step costs cycles and does elements. Scalar costs cycles per element with no setup. Below what is scalar actually faster?
Recall Solution
- Scalar: .
- SIMD: .
- Try small . For : ; → SIMD wins.
- For : ; → tie.
- For : ; → scalar wins. Answer: For scalar is faster; is a tie; SIMD wins. The 12-cycle setup must be amortized over enough elements.
Level 4 — Synthesis
L4.1 — Design a masked branch
Q. You must vectorize if (a[i] > 0) b[i] = a[i]*2; else b[i] = a[i]+1; for 8 floats with AVX,
which forbids per-lane branching. Outline the branch-free lane sequence and count the vector
instructions.
Recall Solution
Compute both results for every lane, then blend using a comparison mask:
mask = cmpgt(a, 0)— 1 op (lanes where become all-ones).t1 = mul(a, 2)— the "then" branch value, 1 op.t2 = add(a, 1)— the "else" branch value, 1 op.b = blend(t2, t1, mask)— pick where mask set, else , 1 op. Total: 4 vector instructions (plus load/store). No branch, all lanes obey one instruction each. Why it works: we trade a control-flow decision (which SIMD hates) for arithmetic on both paths + a select (which SIMD loves). Cost: we always compute the unused branch, but avoid mispredicted per-lane jumps.
L4.2 — Two-phase loop for
Q. Vectorize a for loop over floats with AVX (): give the SIMD iteration count,
the scalar-cleanup count, and total loop iterations.
Recall Solution
- SIMD iterations = number of full trays = iterations, each covering 8 elements → elements handled.
- Elements covered by SIMD = ; scalar cleanup = iterations.
- Total loop iterations = . Answer: 125 SIMD iterations, 0 cleanup, 125 total. (1000 is divisible by 8, so the tail vanishes.) Note the distinction: 125 is the iteration count; the 1000 is the element count they cover — never confuse the two.
L4.3 — Saturating vs wrapping brightness
Q. 8-bit pixels (max value 255). Add brightness 40 to pixel values using (a) wrapping add and (b) saturating add. Give both output vectors.
Recall Solution
- Wrapping (mod 256): ; ; ; . → — bright pixels turn dark (visual garbage).
- Saturating (clamp at 255): ; ; ; .
→ — bright pixels stay bright.
Answer: wrapping ; saturating . Image code always wants saturating (
vqadd).
Level 5 — Mastery
L5.1 — Choose the ISA and justify with numbers
Q. A grayscale filter processes a image, one byte per pixel, doing the same add-and-clamp per pixel. Compare instruction counts for SSE (128-bit, 16 bytes/op), AVX-512 (512-bit, 64 bytes/op), and NEON (128-bit, 16 bytes/op). Which minimizes vector-instruction count, and by what factor over SSE?
Recall Solution
Total pixels bytes.
- SSE / NEON ( bytes): instructions.
- AVX-512 ( bytes): instructions.
- Factor: . Answer: AVX-512 minimizes count with vector ops — 4× fewer than SSE/NEON. Defend the edge cases: is divisible by both 16 and 64, so no scalar tail exists; the image row length (1920) is also a multiple of 64, so every SIMD load is naturally aligned — no unaligned penalty. This is the ideal SIMD workload: uniform op, no branches, aligned, huge .
L5.2 — Amdahl ceiling on a mixed workload
Q. A program spends 80% of its time in a loop you can vectorize at ×; the other 20% is inherently scalar. What is the whole-program speedup? What is the absolute ceiling even if ?
Recall Solution
Let total time . Vectorizable part , scalar part . Ceiling as : the term , leaving Answer: whole-program speedup ×; hard ceiling × (this is Amdahl's law — the 20% scalar part caps everything). Lesson: widening the vector register past a point yields nothing until you also attack the scalar 20%.
L5.3 — Design + defend a robust kernel
Q. Write, in words, a complete robust plan to vectorize c[i] = a[i]*b[i] for arbitrary
using AVX (), guaranteeing correctness for , , and misaligned pointers.
Recall Solution
A correct-for-all-cases plan, in four parts:
- Handle : compute
simd_count = (n/8)*8and run the main loop with guardi < simd_count, then the tail with guardi < n. For bothsimd_count=0andn=0, so both loops run zero iterations — no special-case code needed, but confirm the guards produce zero passes (they do). - Alignment: if the pointers may be misaligned, use the unaligned load (
_mm_loadu_ps/vmovups) — it is safe for any address and only pays the ~2× worst-case bandwidth of L3.2 when a load straddles a cache line. Never use the aligned form (vmovaps) on a pointer you did not align yourself: it faults on a misaligned address. - Main SIMD phase:
simd_count = (n/8)*8; loopfor (i = 0; i < simd_count; i += 8)doing load-a, load-b,vmulps, store-cper tray. This is the fast path that carries the ideal . - Scalar tail:
for (i = simd_count; i < n; i++) c[i] = a[i]*b[i];— this handles every leftover element, so the total is always correct regardless of whether divides 8.
Trace all required edge cases:
- :
simd_count = (0/8)*8 = 0→ SIMD loop skipped, tail loop skipped → 0 work, correct. - :
simd_count = (5/8)*8 = 0→ SIMD skipped, tail does all 5 scalar multiplies → correct. - :
simd_count = (100/8)*8 = 96→ 12 SIMD trays cover 96 elements, tail does the last 4 → correct. - Misaligned pointer:
vmovupsnever faults, so all four cases above still hold at a small bandwidth cost.
Answer: unaligned loads + a simd_count-bounded main loop + a scalar cleanup loop = correct
for every (including 0 and small values) and every alignment, trading a slightly slower
unaligned load for guaranteed safety. Correctness first; tighten to aligned loads only where you
own the allocation and can guarantee the address.
[!recall]- One-line self-check per level
L1 — lanes = register bits ÷ element bits
L2 — use ceiling for trays, never plain division
L3 — real is capped by setup, reduction, alignment
L4 — always add a scalar tail; use saturating int ops
L5 — Amdahl caps whole-program speedup
Related paths: Loop Vectorization · Auto-vectorization · Data Paralelism · Instruction-Level Parallelism · Cache Optimization · Flynn's Taxonomy · GPU Architecture.