o
odin.langpkg.dev
packages / library / x86_64__AVX2_SIMD_Programming_in_Odin

x86_64__AVX2_SIMD_Programming_in_Odin

54191b2library

A comprehensive, progressive learning journey through AVX2 SIMD programming using the Odin programming language on Linux x86_64.

No license · updated 5 months ago

x86_64 AVX2 SIMD Programming in Odin

A comprehensive, progressive learning journey through AVX2 SIMD programming using the Odin programming language on Linux x86_64.

Overview

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

Requirements

  • CPU: x86_64 processor with AVX2 support (Intel Haswell or later, AMD Excavator or later)
  • OS: Linux
  • Compiler: Odin programming language

Check AVX2 Support

make check-avx2

Or manually:

grep avx2 /proc/cpuinfo

Quick Start

# 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 Categories

Fundamentals (00-09)

# 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

Intermediate Operations (10-19)

# 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

Advanced Algorithms (20-29)

# 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

Real-World Applications (30-40)

# 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

Key SIMD Concepts Covered

SIMD Types in Odin

#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)

Essential Operations

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)

Common Patterns

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)

Performance Tips

  1. Use FMA - simd.fused_mul_add() is faster AND more accurate than separate multiply+add
  2. Minimize horizontal operations - Do vertical (lane-wise) ops in loops, horizontal sum only at end
  3. Use multiple accumulators - Hides instruction latency, exploits ILP
  4. Prefer multiplication over division - Multiply by reciprocal when possible
  5. Avoid branches - Use simd.select() with masks instead
  6. Watch alignment - Use intrinsics.unaligned_load/store for safety

Measured Speedups

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

Understanding the Results

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

Makefile Targets

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

Learning Path

  1. Start with 00-09 to understand basic SIMD types and arithmetic
  2. Progress to 10-19 for data manipulation and control flow
  3. Study 20-29 for algorithm patterns and optimizations
  4. Apply knowledge with 30-40 real-world examples

Each example builds on previous concepts, so working through them in order is recommended.

Sample Output

======================================================================
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!

AVX2 Instructions Used

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

Resources

License

These examples are provided has MIT license and are in here for educational purposes.


Happy SIMD programming!