🎓 A1Learner
🔍

Rust Parallel Iterators

A parallel iterator is an iterator from rayon that spreads its work over a thread pool. The chain keeps the same shape as a normal iterator chain.

The prelude brings the methods in

The parallel methods live in traits. One import brings them all in.

use rayon::prelude::*;

One word changes the call

Say nums holds 1, 2, 3 and 4. Swap iter for par_iter. The chain reads the same.

let doubled: Vec<i32> = nums.par_iter().map(|n| n * 2).collect();
println!("doubled = {:?}", doubled);

The order survives

Say words holds "ant", "bee" and "cat". Workers finish in any order. collect still lines the results up as they started.

let loud: Vec<String> = words.par_iter().map(|w| w.to_uppercase()).collect();
println!("loud = {:?}", loud);

Ranges work too

into_par_iter takes ownership of the range. sum adds all one hundred values at once.

let total: i32 = (1..=100).into_par_iter().sum();
println!("total = {}", total);

Change values in place

Start again with 1, 2, 3 and 4 in nums. par_iter_mut hands out mutable references. Each worker edits only its own values.

nums.par_iter_mut().for_each(|n| *n = *n * *n);
println!("squared = {:?}", nums);

Example

// Cargo.toml: rayon = "1"
use rayon::prelude::*;

fn main() {
    let nums = vec![3, 1, 4, 1, 5, 9, 2, 6];
    let squares: Vec<i32> = nums.par_iter().map(|n| n * n).collect();
    let total: i32 = nums.par_iter().sum();
    let biggest: i32 = *nums.par_iter().max().unwrap();
    println!("squares = {:?}", squares);
    println!("total = {}", total);
    println!("biggest = {}", biggest);
}

Key points

  • par_iter borrows the collection.
  • into_par_iter owns it.
  • par_iter_mut borrows it mutably.
  • Use par_iter() to run a chain over every core the machine has
  • Most iterator methods have a parallel twin: map, filter, sum, min, max, count and for_each
  • Rayon splits the data into chunks.
  • An idle worker can steal a chunk from a busy one.
  • A parallel iterator runs one chain on many threads at once.
  • rayon is a crate.
  • It is not part of std.

In this example, nums holds eight numbers. The first call maps each number to its square. collect gathers the squares into a new Vec. The order matches the input order. The second call adds the numbers with sum. The third call finds the biggest with max. Each call ran on the pool. None of the code had to manage a thread.

Note

Add rayon = "1" to Cargo.toml before you import the prelude.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

You change nums.iter() to nums.par_iter() in a map and collect chain. What changes?