Vector - SIMD instructions (SSE, AVX, NEON)
Overview
SIMD (Single Instruction Multiple Data) is a parallel execution model where one instruction operates on multiple data elements simultaneously. Modern CPUs implement this through vector instruction sets like SSE, AVX (x86), and NEON (ARM).
WHY this matters: Processing arrays/matrices element-by-element in loops is slow. SIMD lets you process4, 8, or 16 elements in a single cycle, delivering 4×–16× speedups for media, ML, signal processing, and scientific computing.

[!intuition] The Core Idea
Imagine you're a cashier adding up prices:
- Scalar way: Add one price at a time: 5 + 7 = ... (4 steps)
- SIMD way: Line up four prices, add all four simultaneously in one step
CPUs do this with special wide registers (128-bit, 256-bit, 512-bit) that hold multiple numbers packed together. One instruction adds/multiplies/compares ALL of them at once.
The key constraint: All data elements must undergo the same operation (that's the "Single Instruction" part). Perfect for arrays, not for irregular workloads.
[!definition] SIMD Architecture Components
1. Vector Registers
Wide registers that hold multiple scalar values:
- SSE (Streaming SIMD Extensions): 128-bit XMM registers → 4×32-bit floats or 2× 64-bit doubles
- AVX (Advanced Vector Extensions): 256-bit YMM registers → 8× 32-bit floats
- AVX-512: 512-bit ZMM registers → 16× 32-bit floats
- NEON (ARM): 128-bit Q registers → 4× 32-bit or 16× 8-bit
2. Vector Instructions
Each instruction operates on all lanes of the vector register:
ADDPS xmm0, xmm1 ; Add 4 packed single-precision floats
MULPD ymm2, ymm3 ; Multiply packed double-precision floats
3. Data Packing
Elements are stored contiguously in memory, loaded into vector registers in one operation:
Memory: [a0, a1, a2, a3] → XMM0 = [a0|a1|a2|a3]
[!formula] SIMD Speedup Derivation
WHY derive this? To understand when SIMD helps and by how much.
Starting Point: Scalar Execution Time
For elements, each taking cycles:
SIMD Execution Time
With vector width (elements per register):
- Elements per instruction:
- Number of vector instructions:
- Each vector instruction: cycles (same latency, but processes elements)
WHY ceiling? Partial vectors still need a full instruction.
Theoretical Speedup
For large where :
WHAT this means:
- AVX with 256-bit registers processing 32-bit floats: → 8× speedup
- AVX-512: → 16× speedup
Reality check: Actual speedups are3×–6× due to:
- Memory bandwidth bottlenecks
- Unaligned data requiring extra shuffles
- Loop overhead and scalar remnants
[!example] Example 1: Vector Addition (SSE)
Task: Add two arrays of 4 floats: a[] + b[] = c[]
Scalar Code (pseudo-assembly)
for i = 0 to 3:
load a[i] → register1
load b[i] → register r2
add r1, r2 → r3
store r3 → c[i]Cycles: 4 iterations × (2 loads + 1 add + 1 store) = ~16 operations
SSE Code
movaps xmm0, [a] ; Load all 4 floats from a[] into xmm0
movaps xmm1, [b] ; Load all 4 floats from b[] into xmm1
addps xmm0, xmm1 ; Add all 4 pairs simultaneously
movaps [c], xmm0 ; Store all 4 resultsCycles: 2 loads + 1 add + 1 store = ~4 operations → 4× faster
WHY this works:
movaps= move aligned packed single-precision (requires 16-byte alignment)addps= add packed single-precision, operates on all 4 lanes in parallel- CPU's vector ALU has 4 parallel aders, all fed by one instruction
[!example] Example 2: Dot Product (AVX)
Task: Compute (8 floats)
Derivation from Scalar Operations
Step 1: Element-wise multiply
Step 2: Horizontal sum
AVX Implementation
vmovaps ymm0, [a] ; Load 8 floats from a
vmovaps ymm1, [b] ; Load 8 floats from b
vmulps ymm2, ymm0, ymm1 ; ymm2 = [a0*b0, a1*b1, ..., a7*b7]WHY this step? We've paralelized 8 multiplications into 1 instruction.
; Horizontal add: sum all 8 lanes
vextractf128 xmm3, ymm2, 1 ; Extract upper 4 elements
vaddps xmm2, xmm2, xmm3 ; Add lower 4 + upper 4 →4 sums
vhaddps xmm2, xmm2 ; Horizontal add twice
vhaddps xmm2, xmm2 ; Final sum in xmm2[0]WHY horizontal adds? Vector registers don't automatically sum across lanes. We manually reduce: 8 → 4 → 2 → 1.
Performance: Scalar loop: 8 muls + 7 adds = 15 ops. AVX: 1 mul + 4 reduce ops≈ 3× faster.
[!example] Example 3: Image Brightness (NEON on ARM)
Task: Increase brightness of16 pixels by adding 20 to each (8-bit values).
NEON Code (intrinsics)
uint8x16_t pixels = vld1q_u8(image_ptr); // Load 16 bytes
uint8x16_t brightness = vdupq_n_u8(20); // Duplicate 20 into all 16 lanes
uint8x16_t result = vqaddq_u8(pixels, brightness); // Saturating add
vst1q_u8(output_ptr, result); // Store 16 bytesWHY saturating add (vqadd)? Regular add wraps: 250 + 20 = 14(overflow). Saturating add clamps: 250 + 20 = 255 (capped at max).
Performance: 16 pixels in 4 instructions vs. 16 scalar add/clamp operations.
[!mistake] Common Mistakes
Mistake 1: Ignoring Data Alignment
Wrong idea: "I'll just load any address into SIMD registers."
float *ptr = malloc(17); // Not 16-byte aligned!
__m128 vec = _mm_load_ps(ptr); // CRASH or slow unaligned trapWhy it feels right: Regular loads work with any address.
The fix:
- Use
_mm_loadu_ps()(unaligned load, slower but safe) - OR align data:
float *ptr = aligned_alloc(16, 64);
WHY alignment matters: CPU fetches memory in cache-line-sized chunks (64 bytes). Aligned loads fetch one chunk; unaligned may fetch two, doubling bandwidth.
Mistake 2: Not Handling Loop Remainders
Wrong idea:
for (int i = 0; i < n; i += 8) {
process_8_elements_AVX(arr + i);
}
// If n=10, last 2 elements IGNORED!Why it feels right: Focus on the fast path.
The fix:
int simd_count = (n / 8) * 8;
for (int i = 0; i < simd_count; i += 8) {
process_8_elements_AVX(arr + i);
}
// Scalar cleanup
for (int i = simd_count; i < n; i++) {
process_1_element(arr[i]);
}Steel-man: The scalar tail seems inefficient, but correctness > speed. Modern compilers often auto-vectorize this.
Mistake 3: SIMD for Branchy Code
Wrong idea: "Use SIMD for: if (a[i] > 0) b[i]++; else b[i]--;"
Why it feels right: It's a loop over an array!
The problem: SIMD requires all lanes execute the same instruction. Branches break this. You'd need:
- Vector compare → mask
- Vector increment
- Vector decrement
- Blend results with mask
Result: Slower than scalar code due to overhead.
The fix: SIMD works for data-parallel, branch-free code. Use scalar for control-heavy loops.
[!recall]- Feynman Explanation (Explain to a 12-year-old)
Imagine you're a teacher grading 16 math tests. Normally, you grade one at a time—read answer, mark right/wrong, next test. That's how computers usually work: one number at a time.
But what if you invented a magic table where you lay out 16 tests side-by-side, and when you stamp "CORRECT," it magically stamps all 16 at once? That's SIMD!
The computer has special super-wide calculators (like a calculator that's4 regular calculators glued together). You load4 numbers into it, press "+", and it adds all 4 pairs instantly.
The catch: All 4 numbers must do the same thing. You can't add the first pair, multiply the second, and subtract the third. Same operation for everyone. That's why it's called "Single Instruction, Multiple Data."
Games use this to draw graphics fast (adding colors to millions of pixels). Your phone camera uses it to apply filters to every pixel at once. Any time you need to do the same simple math on a huge list of numbers, SIMD is your friend.
[!mnemonic] Remember SIMD Benefits
"SIMD = Same Instruction, Massive Dividends"
- Speed: 4×–16× faster for array operations
- Ideal for: Images, Media, Math arrays
- Must be: Memory-aligned, branchless
- Data paralelism: Same op on all lanes
Instruction sets:
- SSE: Streaming SIMD Extensions (x86, 128-bit)
- AVX: Advanced Vector eXtensions (x86, 256/512-bit)
- NEON: ARM's answer ("lights up" mobile performance)
Connections
- Flynn's Taxonomy — SIMD is one of the four parallel models (vs MIMD)
- Cache Optimization — SIMD requires contiguous, aligned data for cache efficiency
- GPU Architecture — GPUs are massively-wide SIMD (thousands of ALUs)
- Loop Vectorization — Compiler auto-transforms scalar loops to SIMD
- Data Paralelism — SIMD is the hardware implementation of data-parallel algorithms
- Instruction-Level Parallelism — SIMD complements superscalar/pipelining
- Auto-vectorization — Modern compilers generate SSE/AVX automatically when safe
Key Takeaways
- SIMD = One Instruction, Multiple Data: Process4–16 elements per CPU cycle
- Three major ISAs: SSE (128-bit), AVX (256/512-bit x86), NEON (128-bit ARM)
- Ideal workloads: Dense arrays, no branches, same operation per element
- Speedup formula: where is vector width (elements per register)
- Critical requirements: Data alignment, handling loop remainders, branch-free code
- Real-world uses: Image/video processing, neural networks, physics simulations, audio DSP
Key Takeaways
- SIMD = One Instruction, Multiple Data: Process4–16 elements per CPU cycle
- Three major ISAs: SSE (128-bit), AVX (256/512-bit x86), NEON (128-bit ARM)
- Ideal workloads: Dense arrays, no branches, same operation per element
- Speedup formula: where is vector width (elements per register)
- Critical requirements: Data alignment, handling loop remainders, branch-free code
- Real-world uses: Image/video processing, neural networks, physics simulations, audio DSP
#flashcards/hardware
What does SIMD stand for and what's the core idea? :: Single Instruction Multiple Data — one instruction operates on multiple data elements simultaneously using wide vector registers.
What are the register sizes for SSE, AVX, and AVX-512?
How many 32-bit floats can AVX-256 process in one instruction?
What is the theoretical SIMD speedup formula?
Why must data be aligned for SIMD loads?
What instruction type handles the "add all 4 floats" operation in SSE?
addps (add packed single-precision) operate on all lanes simultaneously.What is a horizontal add and why is it needed for dot products?
What's the key constraint that limits SIMD applicability?
How do you handle loop remainders when n is not a multiple of vector width?
What's a saturating add and why use it in image processing?
Name three application domains where SIMD provides major speedups.
What's the difference between movaps and movups in SSE?
movaps requires 16-byte aligned data (faster), movups handles unaligned data (slower but safe).Why doesn't SIMD work well for code with many branches?
What ARM instruction set provides SIMD capabilities?
How does SIMD relate to Flynn's taxonomy?
Concept Map
Hinglish (regional understanding)
Intuition Hinglish mein samjho
Hinglish (regional understanding)
Intuition Hinglish mein samjho
Dekho, SIMD ka core idea bilkul simple hai — imagine karo ek cashier jo prices add kar raha hai. Normal tarika (scalar) mein woh ek-ek karke add karega: pehle 3, phir 5, phir 2... har number ke liye alag step. Lekin SIMD way mein woh 4 prices ek saath line-up karke ek hi step mein add kar deta hai. CPU ke andar special "wide registers" hote hain (128-bit, 256-bit, ya 512-bit) jo ek saath multiple numbers pack karke rakhte hain, aur ek single instruction un sab par ek saath operation kar deti hai. SSE, AVX (x86 processors ke liye), aur NEON (ARM/mobile phones ke liye) — ye sab isi cheez ke naam hain. Yahan key baat ye hai ki "Single Instruction" — matlab saare elements par same operation hona chahiye, isliye ye arrays aur matrices ke liye perfect hai lekin irregular kaam ke liye nahi.
Ab why-it-matters samjho. Media processing, machine learning, signal processing, scientific computing — in sab mein hume arrays aur matrices par baar-baar same calculation karni padti hai, jaise do arrays add karna ya dot product nikalna. Agar hum loop mein ek-ek element process karein toh bahut slow ho jata hai. SIMD se hum 4, 8, ya 16 elements ek hi cycle mein process kar lete hain, jisse 4x se 16x tak speedup milta hai. Formula bhi simple hai: agar vector width hai (ek register mein kitne elements aate hain) aur elements bahut zyada hain, toh speedup approximately hi hota hai. Jaise AVX 8 floats ek saath = 8x fast, AVX-512 = 16x fast.
Lekin ek reality check bhi yaad rakhna — practical mein aksar 3x se 6x hi speedup milta hai, na ki theoretical 8x ya 16x. Iska reason hai memory bandwidth ki limitation, data ka properly aligned na hona (jisse extra shuffle karna padta hai), aur loop overhead. Toh jab bhi tum performance-critical code likho jismein arrays par heavy math ho, toh SIMD ek powerful tool hai — bas dhyan rakhna ki tumhara data contiguous aur aligned ho taaki maximum benefit mil sake. Ye concept aage jaake GPU programming aur parallel computing samajhne mein bhi bahut help karega.