Rust Performance Engineering

Iterator Optimization

Rust's Iterator trait is designed around two methods with complementary performance characteristics: next(&mut self) (external iteration, where the caller pulls one item at a time) and fold / try_fold (internal iteration, where the iterator drives its own tight internal loop). Understanding how rustc and LLVM optimize iterator adapters unlocks loop fusion, single-pass allocation, and SIMD vectorization.

External Iteration (`while let Some(x) = iter.next()`):
  Caller loop checks adapter state on every single step.
  (Fast for simple `slice::Iter`, but slower for nested `chain` / `flatten` adapters!)

Internal Iteration (`iter.fold(init, f)` / `iter.for_each(f)` / `iter.sum()`):
  Adapter drives its own specialized inner loops (`slice1` loop, then `slice2` loop)
  without checking a state-machine branch on every element!

Four Rules for High-Performance Iterators

Technique Why It Improves Performance Example
1. Prefer fold / for_each / sum on complex adapters Adapters like .chain(...), .flatten(), and VecDeque::iter() consist of multiple contiguous segments. Their custom try_fold / fold implementations run a tight, branchless loop over each segment instead of branching on every .next() call! a.iter().chain(b.iter()).sum()
2. Use chunks_exact(N) instead of chunks(N) chunks(N) yields slices of length 1..=N (because the last chunk may be smaller), forcing the inner loop to handle variable lengths. chunks_exact(N) guarantees every chunk has length exactly N, allowing LLVM to unroll and SIMD-vectorize the chunk body! data.chunks_exact(8) + .remainder()
3. Avoid intermediate .collect::<Vec<_>>() Keep adapter pipelines lazy; collecting halfway through allocates and frees an unnecessary heap buffer. Pass impl Iterator<Item = T> directly.
4. Implement size_hint on custom iterators When .collect::<Vec<_>>() or Vec::extend sees an accurate (lower, Some(upper)) bound from size_hint(), it allocates the final capacity in a single heap call instead of reallocating 6–10 times. fn size_hint(&self) -> (usize, Option<usize>)

Unlocking SIMD Auto-Vectorization with chunks_exact

When summing or transforming blocks of data, slice::chunks_exact(8) tells the compiler that every yielded slice has a compile-time known length of 8 (and slice::as_chunks::<8>() yields &[[T; 8]] directly!):

/// Processes 8-element blocks using `chunks_exact(8)` so LLVM knows the exact
/// block length at compile time and can vectorize the inner loop without remainder branches!
pub fn sum_u32_fast(data: &[u32]) -> u64 {
    let chunks = data.chunks_exact(8);
    let remainder = chunks.remainder();

    let mut lane_sums = [0u64; 8];
    for chunk in chunks {
        for i in 0..8 {
            lane_sums[i] += u64::from(chunk[i]);
        }
    }

    let chunk_total: u64 = lane_sums.iter().sum();
    let rem_total: u64 = remainder.iter().copied().map(u64::from).sum();
    chunk_total + rem_total
}

fn main() {
    let values: Vec<u32> = (1..=100).collect();
    let total = sum_u32_fast(&values);

    assert_eq!(total, 5050);
    println!("Vectorized chunks_exact sum of 1..=100: {total}");
}
Vectorized chunks_exact sum of 1..=100: 5050

Implementing size_hint and ExactSizeIterator

pub struct StepRange {
    current: u32,
    end: u32,
}

impl Iterator for StepRange {
    type Item = u32;

    #[inline]
    fn next(&mut self) -> Option<u32> {
        if self.current < self.end {
            let val = self.current;
            self.current += 1;
            Some(val)
        } else {
            None
        }
    }

    #[inline]
    fn size_hint(&self) -> (usize, Option<usize>) {
        let len = self.end.saturating_sub(self.current) as usize;
        (len, Some(len))
    }
}

impl ExactSizeIterator for StepRange {}

fn main() {
    let iter = StepRange { current: 0, end: 50 };
    assert_eq!(iter.len(), 50);

    // Because `size_hint` returns `(50, Some(50))`, `.collect::<Vec<_>>()`
    // allocates capacity for exactly 50 elements in a single call!
    let collected: Vec<u32> = iter.collect();
    assert_eq!(collected.capacity(), 50);
    println!("Collected 50 items with single allocation (cap={})", collected.capacity());
}
Collected 50 items with single allocation (cap=50)