Course contentsShow
← Rust
Lesson 1420 of 1,91012. Unsafe Rust and Low-Level AccessFree lesson

Vectorization and SIMD Integration

You will use unsafe intrinsics to perform parallel data processing on CPU vector registers.

In this lesson, you will learn how to leverage CPU-level parallelism by performing operations on multiple data points simultaneously using SIMD (Single Instruction, Multiple Data) intrinsics.

The Power of SIMD

Standard programming processes one value at a time. However, modern CPUs feature wide "vector registers"—often 128, 256, or even 512 bits wide—that can hold a "lane" of multiple integers or floats. By using SIMD, you can instruct the processor to add, subtract, or multiply an entire array chunk in a single clock cycle, providing massive speedups for compute-intensive tasks.

In Rust, we access these registers via core::arch intrinsics. Since these instructions operate directly on hardware registers, they are inherently unsafe. You are responsible for ensuring your data is properly aligned and that you don't overflow the memory buffers.

Putting It Into Practice

Imagine adding two arrays of four i32 integers. Instead of a for loop, we can load both arrays into 128-bit registers and perform a single addition.

use core::arch::x86_64::{_mm_add_epi32, _mm_loadu_si128, _mm_storeu_si128};

pub unsafe fn add_arrays(a: &[i32; 4], b: &[i32; 4], result: &mut [i32; 4]) {
    // Load data from pointers into 128-bit vector registers
    let va = _mm_loadu_si128(a.as_ptr() as *const _);
    let vb = _mm_loadu_si128(b.as_ptr() as *const _);

    // Perform SIMD addition across all 4 integers simultaneously
    let v_res = _mm_add_epi32(va, vb);

    // Store the result back into memory
    _mm_storeu_si128(result.as_mut_ptr() as *mut _, v_res);
}

Critical Considerations

When using these intrinsics, you must account for hardware-specific support. If you run _mm_add_epi32 on a processor that doesn't support the SSE2 instruction set, the program will crash (SIGILL). Consequently, you should always wrap your SIMD code in runtime checks (e.g., is_x86_feature_detected!("sse2")) or compile-time target features (#[target_feature(enable = "sse2")]).

While the compiler's auto-vectorizer often handles simple loops, manual SIMD integration via intrinsics allows you to optimize performance-critical hotspots that the compiler might otherwise miss.

Key Takeaway: SIMD allows you to process multiple data elements in a single CPU cycle by treating raw memory as wide vector registers, requiring careful unsafe handling to ensure alignment and hardware compatibility.