A comprehensive, progressive learning journey through AVX2 SIMD programming using the Odin programming language on Linux x86_64.
This repository contains 41 hands-on examples (00-40) that teach you AVX2 SIMD programming from the ground up. Each example includes:
- Scalar implementation - Traditional single-element processing
- SIMD implementation - Parallel processing using AVX2 (256-bit vectors)
- Correctness verification - Both implementations produce identical results
- Performance benchmarking - Measured speedup from SIMD
- Extensive comments - Explanations of concepts, instructions, and patterns
- CPU: x86_64 processor with AVX2 support (Intel Haswell or later, AMD Excavator or later)
- OS: Linux
- Compiler: Odin programming language
make check-avx2Or manually:
grep avx2 /proc/cpuinfo# Build all examples
make all
# Run a specific example
./bin/00_load_store
# Run all examples
make run-all
# Clean build artifacts
make clean| # | Example | Concepts |
|---|---|---|
| 00 | load_store |
Loading/storing SIMD vectors, basic memory operations |
| 01 | integer_addition |
VPADDD instruction, 8-wide integer addition |
| 02 | integer_subtraction |
VPSUBD instruction, broadcasting scalars |
| 03 | integer_multiply |
VPMULLD instruction, overflow considerations |
| 04 | float_addition |
VADDPS/VADDPD, f32x8 and f64x4 types |
| 05 | float_subtraction |
VSUBPS, numerical derivatives |
| 06 | float_multiply |
VMULPS, scaling and powers |
| 07 | float_division |
VDIVPS, reciprocal optimization |
| 08 | fused_multiply_add |
VFMADD - the most important SIMD operation |
| 09 | i16_operations |
16-element vectors with i16x16 |
| # | Example | Concepts |
|---|---|---|
| 10 | bitwise_operations |
AND, OR, XOR, NOT on vectors |
| 11 | shift_operations |
Left/right shifts, bit field extraction |
| 12 | comparison_operations |
Lane comparisons, mask generation |
| 13 | min_max_operations |
Element-wise min/max, clamping |
| 14 | abs_and_negate |
Absolute value, negation, SAD |
| 15 | horizontal_operations |
Horizontal sums, reduction patterns |
| 16 | shuffle_permute |
Data rearrangement, swizzling |
| 17 | blend_operations |
Conditional selection with masks |
| 18 | pack_unpack |
Type conversion, saturation |
| 19 | gather_operations |
Indexed loads, LUT operations |
| # | Example | Concepts |
|---|---|---|
| 20 | array_sum_reduction |
Efficient reduction patterns |
| 21 | dot_product |
FMA-based dot products |
| 22 | vector_normalization |
L2 norm, softmax |
| 23 | matrix_vector_multiply |
Linear algebra foundations |
| 24 | find_min_max |
Parallel search, argmax |
| 25 | counting_elements |
Mask-based counting, bucketing |
| 26 | linear_search |
Parallel element search |
| 27 | polynomial_evaluation |
Horner's method, Taylor series |
| 28 | moving_average |
Sliding window operations |
| 29 | clamp_values |
Range limiting, gradient clipping |
| # | Example | Concepts |
|---|---|---|
| 30 | image_brightness |
Pixel processing, u8↔f32 conversion |
| 31 | alpha_blending |
Compositing, transparency |
| 32 | grayscale_conversion |
Color space transformation |
| 33 | box_blur |
Separable convolution |
| 34 | euclidean_distance |
Distance calculations, kNN |
| 35 | mandelbrot |
Fractal generation, iteration |
| 36 | audio_mixing |
Sample processing, gain control |
| 37 | fast_inv_sqrt |
Vector normalization, Quake algorithm |
| 38 | prefix_sum |
Parallel scan operations |
| 39 | histogram |
Binning, partial histograms |
| 40 | image_convolution |
3×3 kernels, Sobel edge detection |
#simd[8]i32 // 8 × 32-bit integers (256 bits)
#simd[8]f32 // 8 × 32-bit floats (256 bits)
#simd[4]i64 // 4 × 64-bit integers (256 bits)
#simd[4]f64 // 4 × 64-bit doubles (256 bits)
#simd[16]i16 // 16 × 16-bit integers (256 bits)
#simd[32]i8 // 32 × 8-bit integers (256 bits)import "core:simd"
import "base:intrinsics"
// Loading (unaligned for safety)
vec := intrinsics.unaligned_load(cast(^#simd[8]f32)&array[i])
// Storing
intrinsics.unaligned_store(cast(^#simd[8]f32)&array[i], vec)
// Arithmetic
result := vec_a + vec_b // Addition
result := vec_a - vec_b // Subtraction
result := vec_a * vec_b // Multiplication
result := vec_a / vec_b // Division (floats)
// Broadcasting a scalar
scale: #simd[8]f32 = 2.5 // All 8 lanes = 2.5
// Fused Multiply-Add (a*b + c in one instruction)
result := simd.fused_mul_add(a, b, c)
// Comparisons (returns mask)
mask := simd.lanes_gt(a, b) // Greater than
mask := simd.lanes_eq(a, b) // Equal
// Conditional selection
result := simd.select(mask, true_val, false_val)
// Min/Max/Clamp
result := simd.min(a, b)
result := simd.max(a, b)
result := simd.clamp(val, low, high)
// Convert to/from array
arr := simd.to_array(vec)
vec := simd.from_array(arr)1. Process in chunks of 8:
i := 0
for ; i + 8 <= n; i += 8 {
vec := intrinsics.unaligned_load(cast(^#simd[8]f32)&data[i])
// ... process ...
intrinsics.unaligned_store(cast(^#simd[8]f32)&result[i], vec)
}
// Handle remainder
for ; i < n; i += 1 {
result[i] = process_scalar(data[i])
}2. Reduction (sum all elements):
accum: #simd[8]f32 = 0
for ; i + 8 <= n; i += 8 {
vec := intrinsics.unaligned_load(cast(^#simd[8]f32)&data[i])
accum = accum + vec
}
arr := simd.to_array(accum)
total := arr[0] + arr[1] + arr[2] + arr[3] + arr[4] + arr[5] + arr[6] + arr[7]3. Conditional processing:
mask := simd.lanes_gt(vec, threshold)
result := simd.select(mask, clamped_val, vec)- Use FMA -
simd.fused_mul_add()is faster AND more accurate than separate multiply+add - Minimize horizontal operations - Do vertical (lane-wise) ops in loops, horizontal sum only at end
- Use multiple accumulators - Hides instruction latency, exploits ILP
- Prefer multiplication over division - Multiply by reciprocal when possible
- Avoid branches - Use
simd.select()with masks instead - Watch alignment - Use
intrinsics.unaligned_load/storefor safety
All examples tested with -microarch:native -o:speed on x86_64 Linux.
| # | Example | Speedup | Notes |
|---|---|---|---|
| 00 | load_store | 1.25x | Memory-bound operation |
| 01 | integer_addition | 0.96x | Auto-vectorized by compiler |
| 02 | integer_subtraction | 0.98x | Auto-vectorized by compiler |
| 03 | integer_multiply | 0.98x | Auto-vectorized by compiler |
| 04 | float_addition | 0.98x | Auto-vectorized by compiler |
| 05 | float_subtraction | 0.98x | Auto-vectorized by compiler |
| 06 | float_multiply | 0.98x | Auto-vectorized by compiler |
| 07 | float_division | 2.92x | Division benefits from explicit SIMD |
| 08 | fused_multiply_add | 0.99x | Auto-vectorized by compiler |
| 09 | i16_operations | 0.98x | Auto-vectorized by compiler |
| 10 | bitwise_operations | 0.98x | Auto-vectorized by compiler |
| 11 | shift_operations | 1.02x | Auto-vectorized by compiler |
| 12 | comparison_operations | 1.41x | Mask creation benefits SIMD |
| 13 | min_max_operations | 1.02x | Auto-vectorized by compiler |
| 14 | abs_and_negate | 1.02x | Auto-vectorized by compiler |
| 15 | horizontal_operations | 1.89x | Reduction patterns |
| 16 | shuffle_permute | 2.02x | Data rearrangement |
| 17 | blend_operations | 0.96x | Auto-vectorized by compiler |
| 18 | pack_unpack | 0.51x | Type conversion overhead |
| 19 | gather_operations | 0.78x | AVX2 gather is slow |
| 20 | array_sum_reduction | 8.70x | Excellent for reductions |
| 21 | dot_product | 4.66x | FMA accumulation |
| 22 | vector_normalization | 2.68x | Complex math operations |
| 23 | matrix_vector_multiply | 7.14x | Linear algebra |
| 24 | find_min_max | 3.04x | Parallel search |
| 25 | counting_elements | 1.95x | Mask-based counting |
| 26 | linear_search | 1.73x | Early-exit search |
| 27 | polynomial_evaluation | 2.64x | Horner's method |
| 28 | moving_average | 19.27x | Stencil operations |
| 29 | clamp_values | 1.03x | Auto-vectorized by compiler |
| 30 | image_brightness | 0.08x | u8→f32 conversion overhead |
| 31 | alpha_blending | 0.23x | u8→f32 conversion overhead |
| 32 | grayscale_conversion | 1.11x | Scattered RGB access |
| 33 | box_blur | 9.36x | Separable convolution |
| 34 | euclidean_distance | 1.25x | Simple reduction |
| 35 | mandelbrot | 3.81x | Iteration with masks |
| 36 | audio_mixing | 1.01x | Auto-vectorized by compiler |
| 37 | fast_inv_sqrt | 1.70x | Newton-Raphson iteration |
| 38 | prefix_sum | 1.76x | Parallel scan |
| 39 | histogram | 0.96x | Inherently serial (conflicts) |
| 40 | image_convolution | 1.40x | 3×3 kernel application |
Why ~1.0x for simple operations?
The Odin compiler with -o:speed uses LLVM's auto-vectorizer, which is excellent at vectorizing simple loops (add, subtract, multiply, min/max). The explicit SIMD code performs the same operations, resulting in similar performance.
Where SIMD really shines (>2x):
- Reductions (20, 21, 23): Accumulating results with multiple accumulators
- Stencil/convolution (28, 33): Sliding window operations
- Complex conditionals (35): Mandelbrot with per-lane iteration counts
- Data rearrangement (16): Shuffle/permute operations
- Division-heavy (07): Division throughput is limited
Known limitations (<1x):
- Pack/unpack (18): Converting between types has overhead
- Gather (19): AVX2 gather issues multiple memory requests
- u8 image ops (30, 31): u8↔f32 conversion dominates computation
- Histogram (39): Read-modify-write conflicts prevent parallelization
| Target | Description |
|---|---|
make or make all |
Build all examples |
make run-all |
Build and run all examples |
make run EXAMPLE=name |
Run specific example |
make debug |
Build with debug flags |
make clean |
Remove build artifacts |
make check-avx2 |
Verify CPU has AVX2 support |
make help |
Show help message |
- Start with 00-09 to understand basic SIMD types and arithmetic
- Progress to 10-19 for data manipulation and control flow
- Study 20-29 for algorithm patterns and optimizations
- Apply knowledge with 30-40 real-world examples
Each example builds on previous concepts, so working through them in order is recommended.
======================================================================
AVX2 SIMD Example 00: Loading and Storing Vectors
======================================================================
Array size: 10007 elements (40028 bytes)
Running scalar version...
Running SIMD version...
Verifying results...
✓ PASSED: Both versions produce identical results!
Benchmarking (1000 iterations each)...
Scalar time: 881.467µs
SIMD time: 625.234µs
Speedup: 1.41x faster with SIMD!
Throughout these examples, you'll encounter these AVX2 instructions:
| Category | Instructions |
|---|---|
| Load/Store | VMOVDQU, VMOVDQA, VGATHERDPS |
| Integer Arithmetic | VPADDD, VPSUBD, VPMULLD, VPADDW |
| Float Arithmetic | VADDPS, VSUBPS, VMULPS, VDIVPS |
| FMA | VFMADD132PS, VFMADD213PS, VFMADD231PS |
| Bitwise | VPAND, VPOR, VPXOR, VPANDN |
| Shifts | VPSLLD, VPSRLD, VPSRAD |
| Compare | VPCMPEQD, VPCMPGTD, VCMPPS |
| Min/Max | VPMINSD, VPMAXSD, VMINPS, VMAXPS |
| Shuffle | VPSHUFD, VPERMPS, VPERMD |
| Blend | VBLENDVPS, VPBLENDVB |
These examples are provided has MIT license and are in here for educational purposes.
Happy SIMD programming!