Data Parallelism with Rayon
Rayon turns sequential iterators into parallel ones with a work-stealing thread pool. It shines on CPU-bound data processing without manually managing threads.
Recipe
Quick-reference recipe card - copy-paste ready.
use rayon::prelude::*;
fn main() {
let sum: i32 = (1..=1_000_000).into_par_iter().sum();
println!("{sum}");
}When to reach for this: Parallel maps, filters, reductions, and sorting over in-memory collections.
Working Example
use rayon::prelude::*;
#[derive(Debug)]
struct Record {
id: u64,
value: f64,
}
fn main() {
let records: Vec<Record> = (0..100_000)
.map(|i| Record {
id: i,
value: (i as f64).sqrt(),
})
.collect();
let top: Vec<_> = records
.par_iter()
.filter(|r| r.value > 100.0)
.map(|r| r.id)
.collect();
let total: f64 = records.par_iter().map(|r| r.value).sum();
println!("matches = {}, total = {:.2}", top.len(), total);
}What this demonstrates:
par_itersplits work across Rayon's pool.- Combinators mirror sequential iterators.
- Results are deterministic for associative reductions like
sum.
Deep Dive
How It Works
- Work stealing: Idle threads steal tasks from busy queues - good load balance on uneven work.
- Global pool: Default pool sized to CPU cores. Initialize once per process.
- Divide and conquer:
par_itersplits ranges until chunks are small enough. - Send bound: Items and closures must be
Send- same asthread::spawn.
Common APIs
| Method | Purpose |
|---|---|
par_iter() | Parallel immutable iteration |
par_iter_mut() | Parallel in-place mutation |
into_par_iter() | Consume collection in parallel |
par_sort / par_sort_unstable | Parallel sort on slices |
Rust Notes
// Custom thread pool for isolation
use rayon::ThreadPoolBuilder;
let pool = ThreadPoolBuilder::new().num_threads(4).build().unwrap();
pool.install(|| {
let result = data.par_iter().map(expensive).sum::<i32>();
});- Use
pool.installto scope custom pools. - Avoid I/O inside
par_iterclosures - blocks pool threads. ParallelIteratorrequiresSendforTand closures.
Gotchas
- Tiny collections - Thread overhead exceeds savings on 10-item vectors. Fix: Use sequential iterators below a threshold (often thousands of items).
- Blocking I/O in parallel closures - Starves the Rayon pool. Fix: Use async I/O or a dedicated blocking pool (
spawn_blocking). - Non-associative reductions -
sumon floats or custom ops may vary by order. Fix: Use sequential reduce or compensated summation. - Mutex inside par_iter - Serializes work, defeats parallelism. Fix: Use
mapto produce values thenreduce, or fold per partition. - Sharing
Rcacross par_iter -Rcis notSend. Fix: UseArcor process owned data per item.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
thread::scope | Manual chunking with borrows | You want iterator ergonomics |
| Sequential iterators | Small data or cheap per-item work | CPU-bound over large collections |
tokio::task::spawn_blocking | CPU work inside async apps | Pure sync batch jobs |
| SIMD / GPU crates | Numeric kernels at extreme scale | General business logic maps |
FAQs
When does Rayon help?
CPU-bound work over large in-memory data with independent items - parsing, transforms, analytics.
Does order matter?
collect order matches source order. reduce may use different tree order - use associative ops.
Rayon with async?
Call Rayon from spawn_blocking in Tokio - never block the async executor on par_iter directly in hot paths.
Custom thread count?
ThreadPoolBuilder::num_threads(n) - useful to leave cores for other services.
par_iter_mut?
Mutate elements in parallel when each index is independent - no aliasing violations.
Nested parallelism?
Rayon detects nested par_iter and avoids oversubscription in many cases - still profile.
Errors in parallel map?
Collect Result with try_reduce or map to Result then short-circuit with custom fold.
Sort performance?
par_sort_unstable is fast on large slices - ensure Ord is cheap.
Polars relation?
Polars uses its own parallel engine - use Rayon for custom Rust pipelines outside Polars.
Debugging Rayon?
RAYON_NUM_THREADS=1 forces sequential behavior to reproduce race-free logic bugs.
Related
- Concurrency Basics - manual threads
- Scoped Threads - borrow-based parallelism
- Blocking in Async - Rayon in async apps
- Send & Sync - Send requirement
- Concurrency Best Practices - profile before parallelizing
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.