Worked examples — Vector - SIMD instructions (SSE, AVX, NEON)
This page is a drill sheet for the parent SIMD note. There we learned that SIMD means one instruction, many data elements at once, and that the speedup for vector width is roughly . Here we stress-test that idea against every awkward case a real program can throw at it — the clean case, the leftover case, the overflow case, the branchy case, and the exam trick.
Before symbols: let me re-anchor two words so nothing below is a mystery.

The scenario matrix
Every SIMD problem is really a question about how the data lines up with the tray. Here is the full grid of cases — the six worked examples below each fill one or more cells.
| Cell | The scenario it tests | Where it bites | Worked in |
|---|---|---|---|
| C1 | is an exact multiple of | The "clean" ideal case | Ex 1 |
| C2 | is not a multiple of (remainder tail) | , scalar cleanup | Ex 2 |
| C3 | (fewer elements than one tray) | Degenerate: still costs one instruction | Ex 3 |
| C4 | (empty input) | Limiting case: speedup undefined | Ex 3 |
| C5 | Reduction (sum across lanes, not element-wise) | Horizontal add, log-tree cost | Ex 4 |
| C6 | Overflow / saturation on small integers | must clamp, not wrap | Ex 5 |
| C7 | Data-dependent branch inside the loop | Masking; SIMD may lose | Ex 6 |
| C8 | Real-world word problem | Frame budget for image filter | Ex 7 |
| C9 | Exam twist: mixed element sizes / alignment | Trick numbers, the gotcha | Ex 8 |
Example 1 — C1: The clean multiple
Forecast: Before reading on — jot a number. How many vector adds, and what speedup?
- Find . Why this step? The tray size in elements is what divides the work. A 256-bit register 32-bit per float . So .
- Count vector instructions. Why? Each instruction chews through one tray. Number exactly — no ceiling rounding because 8000 is a clean multiple of 8.
- Speedup. Why? Scalar does adds; SIMD does 1000. So .
Verify: Units check — instructions are dimensionless counts. floats processed, matching . ✓
Example 2 — C2: The remainder tail
Forecast: Guess the cleanup count first — is it 3? Something else?
- Full trays. Why? Integer division tells us how many complete groups of 8 fit. full vector adds covering elements.
- Remainder. Why this step? Whatever is left can't fill a tray, so it falls to scalar code. leftover elements → 3 scalar adds.
- Total instruction-equivalents. Why? To compare fairly we count both parts: vector + scalar "adds" worth of work.
- Real speedup. Why? Scalar baseline is adds. .

Verify: ✓. And (the ceiling formula) — note it counts the tail as one padded vector op, giving the slightly-optimistic . Both views are consistent; the scalar-tail view (7.98) is the honest one.
Example 3 — C3 & C4: Fewer than a tray, and empty
Forecast: Does 5 elements still cost a whole instruction? Is the speedup above or below 8?
- Case (a), instruction count. Why ceiling? You cannot issue "5/8 of an instruction" — the hardware runs one whole
addpsand simply ignores (or masks) the 3 empty cups. . - Case (a), speedup. Why? Scalar = 5 adds; SIMD = 1. . Below the ceiling of 8 because 3 lanes were wasted.
- Case (b), the empty input. Why care? Limiting behaviour: gives instructions. Speedup is undefined — there's nothing to accelerate.
Verify: (a) ✓ (speedup equals width times utilization). (b) Any code should guard and skip the loop entirely; no division by zero occurs because the loop body never runs.
Example 4 — C5: Reduction (sum across lanes)
Forecast: Guess the number of reduction stages: 8? 3? 7?
- Why a tree, not a straight line? A vector adds lane-to-lane in parallel, so we fold the 8 values in halves: . Each fold is one vector add.
- Count stages. Why ? Halving repeatedly from 8 needs folds. That's the key gain — a scalar loop needs sequential adds.

- Total AVX op count. Why add the 1? One
vmulpsproduces all 8 products, then 3 reduce-adds. vector ops. - Scalar baseline. Why 15? 8 multiplies + 7 adds scalar ops.
- Speedup. — well below because reductions have that unavoidable -tree overhead.
Recall Why reductions never hit full speedup
Question: Why is a dot product only ~4× on 8-wide SIMD instead of 8× ::: Element-wise multiply parallelizes perfectly (8×), but summing across lanes needs extra folding steps that a scalar loop hides inside its single accumulator — so the wins are diluted.
Verify: stages ✓; scalar ops ✓; AVX ops ✓; ✓.
Example 5 — C6: Overflow vs. saturation
Forecast: What does become in 8 bits when it wraps?
- The true sum. Why? , which exceeds the 8-bit ceiling of 255.
- Wrapping result. Why modulo 256? An 8-bit register holds values –; overflow wraps around by . . A bright pixel turns nearly black — a visible bug.
- Saturating result. Why clamp?
vqadd(the q = saturating) clamps anything above 255 down to 255. So the pixel becomes 255 (pure white) — the physically sensible answer.
Verify: ; ✓; ✓. Register still holds 16 lanes in one instruction regardless of which add variant is chosen.
Example 6 — C7: The branchy loop
Forecast: Both paths do the same work — but which has fewer total ops? Trust your gut.
- Scalar cost. Why 2 per element? One compare, one add-or-sub. Total ops.
- SIMD instruction count. Why 4 per tray? SIMD can't branch, so it computes both outcomes and selects: compare, inc-all, dec-all, blend = 4 vector ops per 8 elements. Number of trays . Total vector ops.
- Compare fairly. Why note "still wins here"? , so SIMD does win on op-count for this clean, predictable branch.
- Why the parent warned anyway. Because SIMD did 2× the useful work (both inc and dec every lane) — utilization is only 50%. If branches are rare or unpredictable, scalar branch prediction can beat this doubled work. The lesson: SIMD-with-masking works, but its speedup is throttled by wasted lanes.
Verify: Scalar ✓; SIMD ✓; ratio (a 4× edge here, but with 50% wasted computation).
Example 7 — C8: Real-world word problem
Forecast: Will the filter eat most of the frame budget, or a tiny sliver?
- Pixels per frame. Why multiply? Image is a grid. pixels.
- NEON instructions per frame. Why divide by 16? Each
vqaddq_u8brightens 16 pixels. instructions (clean multiple — , no tail). - Instructions per second. Why ? 60 frames each second. instructions/s.
- Time per frame. Why? At 1 ns each: .
- Fraction of budget. Why 16.67 ms? A 60 fps frame is ms. Fraction .
Verify: ✓; ✓; ✓; ✓. Units: ns/ms cancels to a pure fraction ✓. The filter is nearly free — leaving the budget for decoding and display.
Example 8 — C9: The exam twist (mixed sizes + alignment)
Forecast: The array size and the address are both traps. Spot them before solving.
- Find — first trap. Why not 4? Students assume from float examples. But these are 16-bit ints: . So .
- Instruction count with tail. Why? full vectors covering elements; remainder scalar ops. So 512 vector + 2 scalar.
- Alignment — second trap. Why check the address bits? Aligned loads need the address divisible by 16. in decimal; . Not aligned →
movapswould fault. You must use the unaligned loadmovups(slower) or re-allocate on a 16-byte boundary.
Verify: ✓; , tail ✓; so aligned load illegal ✓.
Where to go next
- The masking idea in Ex 6 is the heart of Data Paralelism and why Loop Vectorization and Auto-vectorization refuse to touch unpredictable branches.
- The alignment trap in Ex 8 ties directly into Cache Optimization (64-byte cache lines).
- SIMD sits in the "SI-MD" box of Flynn's Taxonomy; when the arrays get huge, you graduate to GPU Architecture. When they stay small but independent, Instruction-Level Parallelism picks up the slack.
Recall Self-test
Ideal speedup of clean on 8-wide AVX ::: exactly 8 Vector instructions for , , plus scalar tail ::: 1250 vector + 3 scalar in 8-bit wrapping add ::: 24 with saturating add ::: 255 NEON instructions to brighten a grayscale frame ::: 129600