Vectors

Rust provides portable SIMD vectors with the Simd type.

Simd<T, N> can be thought of as a variation of [T; N]. Simd has the same “shape” as an array, but may have a greater alignment. In fact, Simd<T, N> is easily convertible to and from [T; N], and supports some of the same operations, such as indexing.

Elementwise operations on vectors

Unlike arrays, Simd<T, N> is not just a container. While it looks like an array, it also operates like T. Vectors implement all of the same basic operators as its scalar type T, meaning you can do the following:

#![feature(portable_simd)]
fn main() {}
use std::simd::f32x4; // an alias for Simd<f32, 4>

/// y = mx + b
fn y(m: f32x4, x: f32x4, b: f32x4) -> f32x4 {
    m * x + b
}

These operators work elementwise, meaning the operator is applied to each element separately.

Vectors even implement special functions (e.g. abs) with traits:

#![feature(portable_simd)]
fn main() {}
use std::simd::{f32x4, SimdFloat}; // SimdFloat provides abs

/// |a - b|
fn distance(a: f32x4, b: f32x4) -> f32x4 {
    (a - b).abs()
}

Reduction operations

Sometimes you may want to perform an operation across a single vector, rather than between vectors. These operations are referred to as reductions:

#![feature(portable_simd)]
fn main() {}
use std::simd::{f32x4, SimdFloat}; // SimdFloat provides reduce_sum

fn sum(vectors: &[f32x4]) -> f32 {
    let mut sums = f32x4::splat(0.0); // splat fills each element with the value
    for v in vectors {
        sums += v;
    }
    
    // `sums` now contains the elementwise sums, so we must sum across the vector
    sums.reduce_sum()
}

Note

Reductions are slower than elementwise operations in most cases. It’s best to use elementwise operations when possible, and use reductions only when necessary.