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?