Rust
Auto-vectorization
Learn compiler auto-vectorization.
Auto-vectorization
LLVM turns suitable loops into SIMD instructions on its own. Conditions that help: access without bounds checks (iterators, chunks_exact), a branchless body, independent lanes. Things that block it: dependencies between iterations, early exits, the order of floating-point addition.
// vectorization-friendly: 8 separate accumulators
fn toplam(v: &[f32]) -> f32 {
let mut birikim = [0.0f32; 8];
let parcalar = v.chunks_exact(8);
let kalan = parcalar.remainder();
for p in parcalar {
for i in 0..8 { birikim[i] += p[i]; }
}
birikim.iter().sum::<f32>() + kalan.iter().sum::<f32>()
}
Target features
The default build uses instructions that run on every x86-64 processor (SSE2). Wider vectors can be enabled for a specific machine:
$ RUSTFLAGS="-C target-cpu=native" cargo build --release
// or for a single function only, with a runtime check:
#[target_feature(enable = "avx2")]
unsafe fn hizli_toplam(v: &[f32]) -> f32 { .. }
if is_x86_feature_detected!("avx2") { unsafe { hizli_toplam(&v) } } else { .. }
A binary built with target-cpu=native may not run on older processors.
Exercise Vectorizable sum
Write the toplam function above with 8 accumulators. Print the sum of a vector holding the numbers 1 to 100 as f32.
Show example solution
fn toplam(v: &[f32]) -> f32 {
let mut birikim = [0.0f32; 8];
let parcalar = v.chunks_exact(8);
let kalan = parcalar.remainder();
for p in parcalar {
for i in 0..8 {
birikim[i] += p[i];
}
}
birikim.iter().sum::<f32>() + kalan.iter().sum::<f32>()
}
fn main() {
let v: Vec<f32> = (1..=100).map(|x| x as f32).collect();
println!("{}", toplam(&v));
}
Test yourself
-
Why doesn't <code>v.iter().sum::<f32>()</code> usually vectorize?
- A single accumulator forms a sequential dependency chain and the f32 order can't be changed
- sum is slow
- It vectorizes
- f32 isn't supported
Show answer
A single accumulator forms a sequential dependency chain and the f32 order can't be changed — Multiple accumulators write the permission to change the order into the code itself.
-
The leftovers
let parcalar = v.chunks_exact(8);
let kalan = parcalar.____();
Show answer
let parcalar = v.chunks_exact(8);
let kalan = parcalar.remainder();
-
Pattern and vectorization
- chunks_exact + birikim dizisi
- Suitable
- Single f32 accumulator
- Usually doesn't vectorize
- break in the body
- Blocks it
- target-cpu=native
- Wide vectors
Next lesson: Summary - SIMD