CodeBlanks Logo Icon CodeBlanks

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.

Practice Vectorizable sum

Write the toplam function above with 8 accumulators. Print the sum of a vector holding the numbers 1 to 100 as f32.

Starter code

main.rs
fn main() {
    // buraya yaz
}

Sample solution

main.rs
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));
}

Expected output

5050

Questions and answers

Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.

1 Why doesn't <code>v.iter().sum::&lt;f32&gt;()</code> usually vectorize?

  • A single accumulator forms a sequential dependency chain and the f32 order can't be changed Correct answer
  • sum is slow
  • It vectorizes
  • f32 isn't supported

Explanation: Multiple accumulators write the permission to change the order into the code itself.

2 The leftovers

Question
let parcalar = v.chunks_exact(8);
let kalan = parcalar.____();
Answer
let parcalar = v.chunks_exact(8);
let kalan = parcalar.remainder();

Explanation: remainder gives the last elements that don't fit into full pieces.

3 Pattern and vectorization

Term Meaning
chunks_exact + birikim dizisi Suitable
Single f32 accumulator Usually doesn't vectorize
break in the body Blocks it
target-cpu=native Wide vectors

All lessons in Expert