#![doc(
html_logo_url = "https://commonware.xyz/imgs/rustdoc_logo.svg",
html_favicon_url = "https://commonware.xyz/favicon.ico"
)]
#![cfg_attr(not(any(feature = "std", test)), no_std)]
commonware_macros::stability_scope!(BETA {
use cfg_if::cfg_if;
use core::{cmp::Ordering, fmt, num::NonZeroUsize};
cfg_if! {
if #[cfg(any(feature = "std", test))] {
use core::convert::Infallible;
use futures::{
channel::oneshot,
future::{self, Either},
};
use rayon::{
iter::{IntoParallelIterator, ParallelIterator},
slice::ParallelSliceMut,
ThreadPool as RThreadPool, ThreadPoolBuildError, ThreadPoolBuilder, Yield,
};
use std::{
panic::{self, AssertUnwindSafe, Location},
sync::Arc,
};
mod policy;
} else {
extern crate alloc;
use alloc::vec::Vec;
}
}
#[derive(Clone, Debug)]
pub struct Manual<S> {
strategy: S,
parallelism: usize,
}
impl<S> Manual<S> {
pub const fn new(strategy: S, parallelism: NonZeroUsize) -> Self {
Self {
strategy,
parallelism: parallelism.get(),
}
}
pub const fn parallelism(&self) -> usize {
self.parallelism
}
}
pub trait Strategy: Clone + Send + Sync + fmt::Debug + 'static {
fn manual(&self) -> Manual<Self>
where
Self: Sized;
fn spawn<F, T>(&self, f: F) -> impl core::future::Future<Output = T> + Send + 'static
where
F: FnOnce(Self) -> T + Send + 'static,
T: Send + 'static;
#[track_caller]
fn run<R, SEQ, PAR>(&self, len: usize, serial: SEQ, parallel: PAR) -> R
where
R: Send,
SEQ: FnOnce() -> R + Send,
PAR: FnOnce() -> R + Send;
#[track_caller]
fn try_run<R, E, SEQ, PAR>(&self, len: usize, serial: SEQ, parallel: PAR) -> Result<R, E>
where
R: Send,
E: Send,
SEQ: FnOnce() -> Result<R, E> + Send,
PAR: FnOnce() -> Result<R, E> + Send;
#[track_caller]
fn fold_init<I, INIT, T, R, ID, F, RD>(
&self,
iter: I,
init: INIT,
identity: ID,
fold_op: F,
reduce_op: RD,
) -> R
where
I: IntoIterator<IntoIter: Send, Item: Send> + Send,
INIT: Fn() -> T + Send + Sync,
T: Send,
R: Send,
ID: Fn() -> R + Send + Sync,
F: Fn(R, &mut T, I::Item) -> R + Send + Sync,
RD: Fn(R, R) -> R + Send + Sync;
#[track_caller]
fn fold<I, R, ID, F, RD>(&self, iter: I, identity: ID, fold_op: F, reduce_op: RD) -> R
where
I: IntoIterator<IntoIter: Send, Item: Send> + Send,
R: Send,
ID: Fn() -> R + Send + Sync,
F: Fn(R, I::Item) -> R + Send + Sync,
RD: Fn(R, R) -> R + Send + Sync,
{
self.fold_init(
iter,
|| (),
identity,
|acc, _, item| fold_op(acc, item),
reduce_op,
)
}
#[track_caller]
fn try_fold<I, R, E, ID, F, RD>(
&self,
iter: I,
identity: ID,
fold_op: F,
reduce_op: RD,
) -> Result<R, E>
where
I: IntoIterator<IntoIter: Send, Item: Send> + Send,
R: Send,
E: Send,
ID: Fn() -> R + Send + Sync,
F: Fn(R, I::Item) -> Result<R, E> + Send + Sync,
RD: Fn(R, R) -> R + Send + Sync;
#[track_caller]
fn map_collect_vec<I, F, T>(&self, iter: I, map_op: F) -> Vec<T>
where
I: IntoIterator<IntoIter: Send, Item: Send> + Send,
F: Fn(I::Item) -> T + Send + Sync,
T: Send,
{
self.fold(
iter,
Vec::new,
|mut acc, item| {
acc.push(map_op(item));
acc
},
|mut a, b| {
a.extend(b);
a
},
)
}
#[track_caller]
fn try_map_collect_vec<I, F, T, E>(&self, iter: I, map_op: F) -> Result<Vec<T>, E>
where
I: IntoIterator<IntoIter: Send, Item: Send> + Send,
F: Fn(I::Item) -> Result<T, E> + Send + Sync,
T: Send,
E: Send,
{
self.try_fold(
iter,
Vec::new,
|mut acc, item| {
acc.push(map_op(item)?);
Ok(acc)
},
|mut a, b| {
a.extend(b);
a
},
)
}
#[track_caller]
fn map_init_collect_vec<I, INIT, T, F, R>(&self, iter: I, init: INIT, map_op: F) -> Vec<R>
where
I: IntoIterator<IntoIter: Send, Item: Send> + Send,
INIT: Fn() -> T + Send + Sync,
T: Send,
F: Fn(&mut T, I::Item) -> R + Send + Sync,
R: Send,
{
self.fold_init(
iter,
init,
Vec::new,
|mut acc, init_val, item| {
acc.push(map_op(init_val, item));
acc
},
|mut a, b| {
a.extend(b);
a
},
)
}
#[track_caller]
fn map_init_collect_vec_with_multiplier<I, INIT, T, F, R>(
&self,
iter: I,
_multiplier: usize,
init: INIT,
map_op: F,
) -> Vec<R>
where
I: IntoIterator<IntoIter: Send, Item: Send> + Send,
INIT: Fn() -> T + Send + Sync,
T: Send,
F: Fn(&mut T, I::Item) -> R + Send + Sync,
R: Send,
{
self.map_init_collect_vec(iter, init, map_op)
}
#[track_caller]
fn map_partition_collect_vec<I, F, K, U>(&self, iter: I, map_op: F) -> (Vec<U>, Vec<K>)
where
I: IntoIterator<IntoIter: Send, Item: Send> + Send,
F: Fn(I::Item) -> (K, Option<U>) + Send + Sync,
K: Send,
U: Send,
{
self.fold(
iter,
|| (Vec::new(), Vec::new()),
|(mut results, mut filtered), item| {
let (key, value) = map_op(item);
match value {
Some(v) => results.push(v),
None => filtered.push(key),
}
(results, filtered)
},
|(mut r1, mut f1), (r2, f2)| {
r1.extend(r2);
f1.extend(f2);
(r1, f1)
},
)
}
fn join<A, B, RA, RB>(&self, a: A, b: B) -> (RA, RB)
where
A: FnOnce() -> RA + Send,
B: FnOnce() -> RB + Send,
RA: Send,
RB: Send;
#[track_caller]
fn sort_by<T, C>(&self, items: &mut [T], compare: C)
where
T: Send,
C: Fn(&T, &T) -> Ordering + Send + Sync;
}
impl<S: Strategy> Strategy for Manual<S> {
fn manual(&self) -> Manual<Self> {
Manual {
strategy: self.clone(),
parallelism: self.parallelism,
}
}
fn spawn<F, T>(&self, f: F) -> impl core::future::Future<Output = T> + Send + 'static
where
F: FnOnce(Self) -> T + Send + 'static,
T: Send + 'static,
{
let s = self.clone();
self.strategy.spawn(|_| f(s))
}
#[track_caller]
fn run<R, SEQ, PAR>(
&self,
len: usize,
serial: SEQ,
parallel: PAR,
) -> R
where
R: Send,
SEQ: FnOnce() -> R + Send,
PAR: FnOnce() -> R + Send,
{
self.strategy.run(len, serial, parallel)
}
#[track_caller]
fn try_run<R, E, SEQ, PAR>(
&self,
len: usize,
serial: SEQ,
parallel: PAR,
) -> Result<R, E>
where
R: Send,
E: Send,
SEQ: FnOnce() -> Result<R, E> + Send,
PAR: FnOnce() -> Result<R, E> + Send,
{
self.strategy.try_run(len, serial, parallel)
}
#[track_caller]
fn fold_init<I, INIT, T, R, ID, F, RD>(
&self,
iter: I,
init: INIT,
identity: ID,
fold_op: F,
reduce_op: RD,
) -> R
where
I: IntoIterator<IntoIter: Send, Item: Send> + Send,
INIT: Fn() -> T + Send + Sync,
T: Send,
R: Send,
ID: Fn() -> R + Send + Sync,
F: Fn(R, &mut T, I::Item) -> R + Send + Sync,
RD: Fn(R, R) -> R + Send + Sync,
{
self.strategy
.fold_init(iter, init, identity, fold_op, reduce_op)
}
#[track_caller]
fn try_fold<I, R, E, ID, F, RD>(
&self,
iter: I,
identity: ID,
fold_op: F,
reduce_op: RD,
) -> Result<R, E>
where
I: IntoIterator<IntoIter: Send, Item: Send> + Send,
R: Send,
E: Send,
ID: Fn() -> R + Send + Sync,
F: Fn(R, I::Item) -> Result<R, E> + Send + Sync,
RD: Fn(R, R) -> R + Send + Sync,
{
self.strategy.try_fold(iter, identity, fold_op, reduce_op)
}
#[track_caller]
fn map_collect_vec<I, F, T>(&self, iter: I, map_op: F) -> Vec<T>
where
I: IntoIterator<IntoIter: Send, Item: Send> + Send,
F: Fn(I::Item) -> T + Send + Sync,
T: Send,
{
self.strategy.map_collect_vec(iter, map_op)
}
#[track_caller]
fn try_map_collect_vec<I, F, T, E>(&self, iter: I, map_op: F) -> Result<Vec<T>, E>
where
I: IntoIterator<IntoIter: Send, Item: Send> + Send,
F: Fn(I::Item) -> Result<T, E> + Send + Sync,
T: Send,
E: Send,
{
self.strategy.try_map_collect_vec(iter, map_op)
}
#[track_caller]
fn map_init_collect_vec<I, INIT, T, F, R>(&self, iter: I, init: INIT, map_op: F) -> Vec<R>
where
I: IntoIterator<IntoIter: Send, Item: Send> + Send,
INIT: Fn() -> T + Send + Sync,
T: Send,
F: Fn(&mut T, I::Item) -> R + Send + Sync,
R: Send,
{
self.strategy.map_init_collect_vec(iter, init, map_op)
}
#[track_caller]
fn map_init_collect_vec_with_multiplier<I, INIT, T, F, R>(
&self,
iter: I,
multiplier: usize,
init: INIT,
map_op: F,
) -> Vec<R>
where
I: IntoIterator<IntoIter: Send, Item: Send> + Send,
INIT: Fn() -> T + Send + Sync,
T: Send,
F: Fn(&mut T, I::Item) -> R + Send + Sync,
R: Send,
{
self.strategy
.map_init_collect_vec_with_multiplier(iter, multiplier, init, map_op)
}
#[track_caller]
fn map_partition_collect_vec<I, F, K, U>(&self, iter: I, map_op: F) -> (Vec<U>, Vec<K>)
where
I: IntoIterator<IntoIter: Send, Item: Send> + Send,
F: Fn(I::Item) -> (K, Option<U>) + Send + Sync,
K: Send,
U: Send,
{
self.strategy.map_partition_collect_vec(iter, map_op)
}
fn join<A, B, RA, RB>(&self, a: A, b: B) -> (RA, RB)
where
A: FnOnce() -> RA + Send,
B: FnOnce() -> RB + Send,
RA: Send,
RB: Send,
{
self.strategy.join(a, b)
}
#[track_caller]
fn sort_by<T, C>(&self, items: &mut [T], compare: C)
where
T: Send,
C: Fn(&T, &T) -> Ordering + Send + Sync,
{
self.strategy.sort_by(items, compare)
}
}
#[derive(Default, Debug, Clone)]
pub struct Sequential;
impl Strategy for Sequential {
fn manual(&self) -> Manual<Self> {
Manual::new(Self, NonZeroUsize::new(1).unwrap())
}
fn spawn<F, T>(&self, f: F) -> impl core::future::Future<Output = T> + Send + 'static
where
F: FnOnce(Self) -> T + Send + 'static,
T: Send + 'static,
{
let result = f(self.clone());
async move { result }
}
fn run<R, SEQ, PAR>(&self, _len: usize, serial: SEQ, _parallel: PAR) -> R
where
R: Send,
SEQ: FnOnce() -> R + Send,
PAR: FnOnce() -> R + Send,
{
serial()
}
fn try_run<R, E, SEQ, PAR>(&self, _len: usize, serial: SEQ, _parallel: PAR) -> Result<R, E>
where
R: Send,
E: Send,
SEQ: FnOnce() -> Result<R, E> + Send,
PAR: FnOnce() -> Result<R, E> + Send,
{
serial()
}
fn fold_init<I, INIT, T, R, ID, F, RD>(
&self,
iter: I,
init: INIT,
identity: ID,
fold_op: F,
_reduce_op: RD,
) -> R
where
I: IntoIterator<IntoIter: Send, Item: Send> + Send,
INIT: Fn() -> T + Send + Sync,
T: Send,
R: Send,
ID: Fn() -> R + Send + Sync,
F: Fn(R, &mut T, I::Item) -> R + Send + Sync,
RD: Fn(R, R) -> R + Send + Sync,
{
let mut init_val = init();
iter.into_iter()
.fold(identity(), |acc, item| fold_op(acc, &mut init_val, item))
}
fn try_fold<I, R, E, ID, F, RD>(
&self,
iter: I,
identity: ID,
fold_op: F,
_reduce_op: RD,
) -> Result<R, E>
where
I: IntoIterator<IntoIter: Send, Item: Send> + Send,
R: Send,
E: Send,
ID: Fn() -> R + Send + Sync,
F: Fn(R, I::Item) -> Result<R, E> + Send + Sync,
RD: Fn(R, R) -> R + Send + Sync,
{
iter.into_iter().try_fold(identity(), fold_op)
}
fn join<A, B, RA, RB>(&self, a: A, b: B) -> (RA, RB)
where
A: FnOnce() -> RA + Send,
B: FnOnce() -> RB + Send,
RA: Send,
RB: Send,
{
(a(), b())
}
fn sort_by<T, C>(&self, items: &mut [T], compare: C)
where
T: Send,
C: Fn(&T, &T) -> Ordering + Send + Sync,
{
items.sort_by(compare);
}
}
});
commonware_macros::stability_scope!(BETA, cfg(any(feature = "std", test)) {
pub type ThreadPool = Arc<RThreadPool>;
#[derive(Debug, Clone)]
pub struct Rayon {
thread_pool: ThreadPool,
parallelism: usize,
policy: Option<policy::Policy>,
}
impl Rayon {
pub fn new(num_threads: NonZeroUsize) -> Result<Self, ThreadPoolBuildError> {
ThreadPoolBuilder::new()
.num_threads(num_threads.get())
.build()
.map(|pool| Self::with_pool(Arc::new(pool)))
}
pub fn with_pool(thread_pool: ThreadPool) -> Self {
let parallelism = thread_pool.current_num_threads().max(1);
Self {
thread_pool,
parallelism,
policy: Some(policy::Policy::default()),
}
}
pub const fn with_parallelism(mut self, parallelism: NonZeroUsize) -> Self {
self.parallelism = parallelism.get();
self
}
#[track_caller]
fn execute<R>(
&self,
len: usize,
multiplier: usize,
run: impl FnOnce(policy::Execution) -> R,
) -> R {
match self.try_execute(len, multiplier, |execution| {
Ok::<_, Infallible>(run(execution))
}) {
Ok(result) => result,
Err(e) => match e {},
}
}
#[track_caller]
fn try_execute<R, E>(
&self,
len: usize,
multiplier: usize,
run: impl FnOnce(policy::Execution) -> Result<R, E>,
) -> Result<R, E> {
let Some(policy) = &self.policy else {
let execution = if self.parallelism <= 1 {
policy::Execution::Serial
} else {
policy::Execution::Parallel
};
return run(execution);
};
let work = len.saturating_mul(multiplier);
policy.try_run(Location::caller(), len, work, self.parallelism, run)
}
}
impl Strategy for Rayon {
fn manual(&self) -> Manual<Self> {
Manual {
strategy: Self {
thread_pool: self.thread_pool.clone(),
parallelism: self.parallelism,
policy: None,
},
parallelism: self.parallelism,
}
}
fn spawn<F, T>(&self, f: F) -> impl core::future::Future<Output = T> + Send + 'static
where
F: FnOnce(Self) -> T + Send + 'static,
T: Send + 'static,
{
if self.thread_pool.current_num_threads() <= 1 {
return Either::Left(future::ready(f(self.clone())));
}
let (tx, mut rx) = oneshot::channel();
let s = self.clone();
let pool = self.thread_pool.clone();
self.thread_pool.spawn(move || {
let result = panic::catch_unwind(AssertUnwindSafe(|| f(s)));
let _ = tx.send(result);
});
Either::Right(async move {
loop {
if let Ok(Some(result)) = rx.try_recv() {
return match result {
Ok(value) => value,
Err(payload) => panic::resume_unwind(payload),
};
}
if !matches!(pool.yield_now(), Some(Yield::Executed)) {
break;
}
}
match rx.await {
Ok(Ok(value)) => value,
Ok(Err(payload)) => panic::resume_unwind(payload),
Err(_) => panic!("strategy job dropped before completion"),
}
})
}
#[track_caller]
fn run<R, SEQ, PAR>(
&self,
len: usize,
serial: SEQ,
parallel: PAR,
) -> R
where
R: Send,
SEQ: FnOnce() -> R + Send,
PAR: FnOnce() -> R + Send,
{
self.execute(len, 1, |execution| match execution {
policy::Execution::Serial => serial(),
policy::Execution::Parallel => parallel(),
})
}
#[track_caller]
fn try_run<R, E, SEQ, PAR>(
&self,
len: usize,
serial: SEQ,
parallel: PAR,
) -> Result<R, E>
where
R: Send,
E: Send,
SEQ: FnOnce() -> Result<R, E> + Send,
PAR: FnOnce() -> Result<R, E> + Send,
{
self.try_execute(len, 1, |execution| match execution {
policy::Execution::Serial => serial(),
policy::Execution::Parallel => parallel(),
})
}
#[track_caller]
fn fold_init<I, INIT, T, R, ID, F, RD>(
&self,
iter: I,
init: INIT,
identity: ID,
fold_op: F,
reduce_op: RD,
) -> R
where
I: IntoIterator<IntoIter: Send, Item: Send> + Send,
INIT: Fn() -> T + Send + Sync,
T: Send,
R: Send,
ID: Fn() -> R + Send + Sync,
F: Fn(R, &mut T, I::Item) -> R + Send + Sync,
RD: Fn(R, R) -> R + Send + Sync,
{
let items: Vec<I::Item> = iter.into_iter().collect();
self.execute(items.len(), 1, |execution| match execution {
policy::Execution::Serial => Sequential.fold_init(items, init, identity, fold_op, reduce_op),
policy::Execution::Parallel => self.thread_pool.install(|| {
items
.into_par_iter()
.fold(
|| (init(), identity()),
|(mut init_val, acc), item| {
let new_acc = fold_op(acc, &mut init_val, item);
(init_val, new_acc)
},
)
.map(|(_, acc)| acc)
.reduce(&identity, reduce_op)
}),
})
}
#[track_caller]
fn map_collect_vec<I, F, T>(&self, iter: I, map_op: F) -> Vec<T>
where
I: IntoIterator<IntoIter: Send, Item: Send> + Send,
F: Fn(I::Item) -> T + Send + Sync,
T: Send,
{
let items: Vec<I::Item> = iter.into_iter().collect();
self.execute(
items.len(),
1,
|execution| {
match execution {
policy::Execution::Serial => Sequential.map_collect_vec(items, map_op),
policy::Execution::Parallel => self
.thread_pool
.install(|| items.into_par_iter().map(map_op).collect()),
}
},
)
}
#[track_caller]
fn try_map_collect_vec<I, F, T, E>(&self, iter: I, map_op: F) -> Result<Vec<T>, E>
where
I: IntoIterator<IntoIter: Send, Item: Send> + Send,
F: Fn(I::Item) -> Result<T, E> + Send + Sync,
T: Send,
E: Send,
{
let items: Vec<I::Item> = iter.into_iter().collect();
self.try_execute(
items.len(),
1,
|execution| {
match execution {
policy::Execution::Serial => Sequential.try_map_collect_vec(items, map_op),
policy::Execution::Parallel => self
.thread_pool
.install(|| items.into_par_iter().map(map_op).collect()),
}
},
)
}
#[track_caller]
fn map_init_collect_vec<I, INIT, T, F, R>(&self, iter: I, init: INIT, map_op: F) -> Vec<R>
where
I: IntoIterator<IntoIter: Send, Item: Send> + Send,
INIT: Fn() -> T + Send + Sync,
T: Send,
F: Fn(&mut T, I::Item) -> R + Send + Sync,
R: Send,
{
let items: Vec<I::Item> = iter.into_iter().collect();
self.execute(
items.len(),
1,
|execution| {
match execution {
policy::Execution::Serial => Sequential.map_init_collect_vec(items, init, map_op),
policy::Execution::Parallel => self
.thread_pool
.install(|| items.into_par_iter().map_init(init, map_op).collect()),
}
},
)
}
#[track_caller]
fn map_init_collect_vec_with_multiplier<I, INIT, T, F, R>(
&self,
iter: I,
multiplier: usize,
init: INIT,
map_op: F,
) -> Vec<R>
where
I: IntoIterator<IntoIter: Send, Item: Send> + Send,
INIT: Fn() -> T + Send + Sync,
T: Send,
F: Fn(&mut T, I::Item) -> R + Send + Sync,
R: Send,
{
let items: Vec<I::Item> = iter.into_iter().collect();
self.execute(
items.len(),
multiplier,
|execution| {
match execution {
policy::Execution::Serial => {
Sequential.map_init_collect_vec(items, init, map_op)
}
policy::Execution::Parallel => self
.thread_pool
.install(|| items.into_par_iter().map_init(init, map_op).collect()),
}
},
)
}
#[track_caller]
fn try_fold<I, R, E, ID, F, RD>(
&self,
iter: I,
identity: ID,
fold_op: F,
reduce_op: RD,
) -> Result<R, E>
where
I: IntoIterator<IntoIter: Send, Item: Send> + Send,
R: Send,
E: Send,
ID: Fn() -> R + Send + Sync,
F: Fn(R, I::Item) -> Result<R, E> + Send + Sync,
RD: Fn(R, R) -> R + Send + Sync,
{
let items: Vec<I::Item> = iter.into_iter().collect();
self.try_execute(items.len(), 1, |execution| match execution {
policy::Execution::Serial => Sequential.try_fold(items, identity, fold_op, reduce_op),
policy::Execution::Parallel => self.thread_pool.install(|| {
items
.into_par_iter()
.try_fold(&identity, &fold_op)
.try_reduce(&identity, |a, b| Ok(reduce_op(a, b)))
}),
})
}
fn join<A, B, RA, RB>(&self, a: A, b: B) -> (RA, RB)
where
A: FnOnce() -> RA + Send,
B: FnOnce() -> RB + Send,
RA: Send,
RB: Send,
{
self.thread_pool.install(|| rayon::join(a, b))
}
#[track_caller]
fn sort_by<T, C>(&self, items: &mut [T], compare: C)
where
T: Send,
C: Fn(&T, &T) -> Ordering + Send + Sync,
{
self.execute(
items.len(),
1,
|execution| match execution {
policy::Execution::Serial => Sequential.sort_by(items, compare),
policy::Execution::Parallel => self.thread_pool.install(|| items.par_sort_by(compare)),
},
);
}
}
});
#[cfg(test)]
mod test {
use crate::{Rayon, Sequential, Strategy};
use core::num::NonZeroUsize;
use futures::FutureExt;
use proptest::prelude::*;
use rayon::ThreadPoolBuilder;
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};
fn parallel_strategy() -> Rayon {
Rayon::new(NonZeroUsize::new(4).unwrap()).unwrap()
}
fn policy_len(strategy: &Rayon) -> usize {
strategy.policy.as_ref().map_or(0, |policy| policy.len())
}
fn map_from_same_callsite(strategy: &Rayon, len: usize) {
let _: Vec<_> = strategy.map_collect_vec(0..len, |x| x);
}
fn map_init_with_multiplier_from_same_callsite(
strategy: &Rayon,
len: usize,
multiplier: usize,
) {
let _: Vec<_> =
strategy.map_init_collect_vec_with_multiplier(0..len, multiplier, || (), |_, x| x);
}
fn run_from_same_callsite(strategy: &Rayon, len: usize) {
let _: usize = strategy.run(len, || 1, || 2);
}
fn map_partition_from_same_callsite(strategy: &Rayon, len: usize) {
let _: (Vec<_>, Vec<_>) = strategy.map_partition_collect_vec(0..len, |x| {
if x % 2 == 0 {
(x, Some(x))
} else {
(x, None)
}
});
}
#[test]
fn adaptive_policy_is_scoped_to_rayon() {
let strategy = parallel_strategy();
let other = parallel_strategy();
let _: Vec<_> = strategy.map_collect_vec(0..16, |x| x);
assert_eq!(policy_len(&strategy), 1);
assert_eq!(policy_len(&other), 0);
}
#[test]
fn spawn_driven_inline_on_member_thread() {
let pool = ThreadPoolBuilder::new()
.num_threads(2)
.use_current_thread()
.spawn_handler(|_| Ok(()))
.build()
.unwrap();
let strategy = Rayon::with_pool(Arc::new(pool));
let result = strategy
.spawn(|strategy| strategy.map_collect_vec(0..2, |i| i + 1))
.now_or_never()
.expect("spawn should complete on first poll via the yield loop");
assert_eq!(result, vec![1, 2]);
}
#[test]
fn with_parallelism_overrides_planning_parallelism() {
let strategy = Rayon::new(NonZeroUsize::new(1).unwrap())
.unwrap()
.with_parallelism(NonZeroUsize::new(4).unwrap());
let strategy = strategy.manual();
assert_eq!(strategy.parallelism(), 4);
assert_eq!(strategy.run(2, || "serial", || "parallel"), "parallel");
}
#[test]
fn adaptive_policy_is_shared_by_clones() {
let strategy = parallel_strategy();
let clone = strategy.clone();
let _: Vec<_> = clone.map_collect_vec(0..16, |x| x);
assert_eq!(policy_len(&strategy), 1);
assert_eq!(policy_len(&clone), 1);
}
#[test]
fn adaptive_policy_records_all_adaptive_operations() {
let strategy = parallel_strategy();
let _: Vec<_> = strategy.fold_init(
0..16,
|| (),
Vec::new,
|mut acc, _, x| {
acc.push(x);
acc
},
|mut a, b| {
a.extend(b);
a
},
);
let _: i32 = strategy.fold(0..16, || 0, |acc, x| acc + x, |a, b| a + b);
let _: Result<i32, ()> = strategy.try_fold(0..16, || 0, |acc, x| Ok(acc + x), |a, b| a + b);
let _: Vec<_> = strategy.map_collect_vec(0..16, |x| x);
let _: Result<Vec<_>, ()> = strategy.try_map_collect_vec(0..16, Ok);
let _: Vec<_> = strategy.map_init_collect_vec(
0..16,
|| AtomicUsize::new(0),
|counter, x| {
counter.fetch_add(1, Ordering::Relaxed);
x
},
);
let _: Vec<_> = strategy.map_init_collect_vec_with_multiplier(
0..16,
2,
|| AtomicUsize::new(0),
|counter, x| {
counter.fetch_add(1, Ordering::Relaxed);
x
},
);
let _: usize = strategy.run(16, || 1, || 2);
let _: (Vec<_>, Vec<_>) = strategy.map_partition_collect_vec(0..16, |x| {
if x % 2 == 0 {
(x, Some(x))
} else {
(x, None)
}
});
let _: (i32, i32) = strategy.join(|| 1, || 2);
let mut sortable = vec![3, 2, 1];
strategy.sort_by(&mut sortable, |a, b| a.cmp(b));
assert_eq!(sortable, vec![1, 2, 3]);
assert_eq!(policy_len(&strategy), 10);
}
#[test]
fn adaptive_policy_buckets_by_input_size() {
let strategy = parallel_strategy();
map_from_same_callsite(&strategy, 1);
map_from_same_callsite(&strategy, 2);
map_from_same_callsite(&strategy, 3);
assert_eq!(policy_len(&strategy), 2);
}
#[test]
fn adaptive_policy_buckets_by_work_multiplier() {
let strategy = parallel_strategy();
map_init_with_multiplier_from_same_callsite(&strategy, 16, 1);
map_init_with_multiplier_from_same_callsite(&strategy, 16, 2);
map_init_with_multiplier_from_same_callsite(&strategy, 16, 3);
assert_eq!(policy_len(&strategy), 2);
}
#[test]
fn adaptive_run_buckets_by_input_size() {
let strategy = parallel_strategy();
run_from_same_callsite(&strategy, 1);
run_from_same_callsite(&strategy, 2);
run_from_same_callsite(&strategy, 3);
assert_eq!(policy_len(&strategy), 2);
}
#[test]
fn manual_strategy_does_not_use_adaptive_policy() {
let strategy = parallel_strategy();
let manual = strategy.manual();
let _: usize = manual.fold(0..4, || 0, |acc, x| acc + x, |a, b| a + b);
assert_eq!(manual.run(4, || 1, || 2), 2);
assert_eq!(policy_len(&strategy), 0);
assert_eq!(policy_len(&manual.strategy), 0);
}
#[test]
fn sequential_run_uses_serial_body() {
assert_eq!(Sequential.run(4, || 1, || 2), 1);
}
#[test]
fn adaptive_policy_keys_default_methods_by_external_callsite() {
let strategy = parallel_strategy();
let _: i32 = strategy.fold(0..16, || 0, |acc, x| acc + x, |a, b| a + b);
let _: i32 = strategy.fold(0..16, || 0, |acc, x| acc + x, |a, b| a + b);
assert_eq!(policy_len(&strategy), 2);
}
#[test]
fn adaptive_policy_keys_partition_map_by_external_callsite() {
let strategy = parallel_strategy();
map_partition_from_same_callsite(&strategy, 16);
let _: (Vec<_>, Vec<_>) = strategy.map_partition_collect_vec(0..16, |x| {
if x % 2 == 0 {
(x, Some(x))
} else {
(x, None)
}
});
assert_eq!(policy_len(&strategy), 2);
}
#[test]
fn join_does_not_use_adaptive_policy() {
let strategy = parallel_strategy();
let result = strategy.join(|| 1, || 2);
assert_eq!(result, (1, 2));
assert_eq!(policy_len(&strategy), 0);
}
#[test]
fn sequential_spawn_runs_job() {
let result = futures::executor::block_on(Sequential.spawn(|_| 7));
assert_eq!(result, 7);
}
#[test]
fn rayon_spawn_runs_job_on_pool() {
let strategy = parallel_strategy();
let result = futures::executor::block_on(strategy.spawn(|_| {
assert!(rayon::current_thread_index().is_some());
7
}));
assert_eq!(result, 7);
assert_eq!(policy_len(&strategy), 0);
}
#[test]
fn rayon_spawn_runs_inline_on_current_thread_single_worker_pool() {
let pool = ThreadPoolBuilder::new()
.num_threads(1)
.use_current_thread()
.build()
.unwrap();
let strategy =
Rayon::with_pool(Arc::new(pool)).with_parallelism(NonZeroUsize::new(4).unwrap());
assert_eq!(strategy.manual().parallelism(), 4);
let result = strategy.spawn(|_| 7).now_or_never();
assert_eq!(result, Some(7));
assert_eq!(policy_len(&strategy), 0);
}
#[test]
#[should_panic(expected = "boom")]
fn rayon_spawn_propagates_job_panic() {
let strategy = parallel_strategy();
let _: () = futures::executor::block_on(strategy.spawn(|_| panic!("boom")));
}
#[test]
#[should_panic(expected = "boom")]
fn sequential_spawn_propagates_job_panic() {
let _: () = futures::executor::block_on(Sequential.spawn(|_| panic!("boom")));
}
proptest! {
#[test]
fn parallel_fold_init_matches_sequential(data in prop::collection::vec(any::<i32>(), 0..500)) {
let sequential = Sequential;
let parallel = parallel_strategy();
let seq_result: Vec<i32> = sequential.fold_init(
&data,
|| (),
Vec::new,
|mut acc, _, &x| { acc.push(x.wrapping_mul(2)); acc },
|mut a, b| { a.extend(b); a },
);
let par_result: Vec<i32> = parallel.fold_init(
&data,
|| (),
Vec::new,
|mut acc, _, &x| { acc.push(x.wrapping_mul(2)); acc },
|mut a, b| { a.extend(b); a },
);
prop_assert_eq!(seq_result, par_result);
}
#[test]
fn fold_equals_fold_init(data in prop::collection::vec(any::<i32>(), 0..500)) {
let s = Sequential;
let via_fold: Vec<i32> = s.fold(
&data,
Vec::new,
|mut acc, &x| { acc.push(x); acc },
|mut a, b| { a.extend(b); a },
);
let via_fold_init: Vec<i32> = s.fold_init(
&data,
|| (),
Vec::new,
|mut acc, _, &x| { acc.push(x); acc },
|mut a, b| { a.extend(b); a },
);
prop_assert_eq!(via_fold, via_fold_init);
}
#[test]
fn parallel_try_fold_matches_sequential(data in prop::collection::vec(any::<i32>(), 0..500)) {
let sequential: Result<i32, ()> = Sequential.try_fold(
&data,
|| 0i32,
|acc, &x| Ok(acc.wrapping_add(x)),
|a, b| a.wrapping_add(b),
);
let parallel: Result<i32, ()> = parallel_strategy().try_fold(
&data,
|| 0i32,
|acc, &x| Ok(acc.wrapping_add(x)),
|a, b| a.wrapping_add(b),
);
prop_assert_eq!(sequential, parallel);
}
#[test]
fn map_collect_vec_equals_fold(data in prop::collection::vec(any::<i32>(), 0..500)) {
let s = Sequential;
let map_op = |&x: &i32| x.wrapping_mul(3);
let via_map: Vec<i32> = s.map_collect_vec(&data, map_op);
let via_fold: Vec<i32> = s.fold(
&data,
Vec::new,
|mut acc, item| { acc.push(map_op(item)); acc },
|mut a, b| { a.extend(b); a },
);
prop_assert_eq!(via_map, via_fold);
}
#[test]
fn try_map_collect_vec_collects_successes(data in prop::collection::vec(any::<i32>(), 0..500)) {
let expected: Vec<i32> = data.iter().map(|x| x.wrapping_mul(5)).collect();
let sequential: Result<Vec<i32>, ()> =
Sequential.try_map_collect_vec(&data, |&x| Ok(x.wrapping_mul(5)));
prop_assert_eq!(sequential, Ok(expected.clone()));
let parallel: Result<Vec<i32>, ()> =
parallel_strategy().try_map_collect_vec(&data, |&x| Ok(x.wrapping_mul(5)));
prop_assert_eq!(parallel, Ok(expected));
}
#[test]
fn try_map_collect_vec_returns_first_error(data in prop::collection::vec(any::<i32>(), 0..500)) {
let expected_error = data.iter().position(|x| x % 7 == 0);
let result: Result<Vec<i32>, usize> =
Sequential.try_map_collect_vec(data.iter().enumerate(), |(i, &x)| {
if x % 7 == 0 {
Err(i)
} else {
Ok(x)
}
});
match expected_error {
Some(i) => prop_assert_eq!(result, Err(i)),
None => prop_assert_eq!(result, Ok(data)),
}
}
#[test]
fn map_init_collect_vec_equals_fold_init(data in prop::collection::vec(any::<i32>(), 0..500)) {
let s = Sequential;
let via_map: Vec<i32> = s.map_init_collect_vec(
&data,
|| 0i32,
|counter, &x| { *counter += 1; x.wrapping_add(*counter) },
);
let via_fold_init: Vec<i32> = s.fold_init(
&data,
|| 0i32,
Vec::new,
|mut acc, counter, &x| {
*counter += 1;
acc.push(x.wrapping_add(*counter));
acc
},
|mut a, b| { a.extend(b); a },
);
prop_assert_eq!(via_map, via_fold_init);
}
#[test]
fn map_partition_collect_vec_returns_valid_results(data in prop::collection::vec(any::<i32>(), 0..500)) {
let s = Sequential;
let map_op = |&x: &i32| {
let value = if x % 2 == 0 { Some(x.wrapping_mul(2)) } else { None };
(x, value)
};
let (results, filtered) = s.map_partition_collect_vec(data.iter(), map_op);
let expected_results: Vec<i32> = data.iter().filter(|&&x| x % 2 == 0).map(|&x| x.wrapping_mul(2)).collect();
prop_assert_eq!(results, expected_results);
let expected_filtered: Vec<i32> = data.iter().filter(|&&x| x % 2 != 0).copied().collect();
prop_assert_eq!(filtered, expected_filtered);
}
}
#[test]
fn try_map_collect_vec_sequential_short_circuits() {
let calls = AtomicUsize::new(0);
let result: Result<Vec<usize>, usize> = Sequential.try_map_collect_vec(0..10, |i| {
calls.fetch_add(1, Ordering::Relaxed);
if i == 3 {
Err(i)
} else {
Ok(i)
}
});
assert_eq!(result, Err(3));
assert_eq!(calls.load(Ordering::Relaxed), 4);
}
#[test]
fn try_map_collect_vec_parallel_returns_an_error() {
let result: Result<Vec<usize>, usize> =
parallel_strategy().try_map_collect_vec(0..128, |i| {
if i == 17 || i == 42 {
Err(i)
} else {
Ok(i)
}
});
assert!(matches!(result, Err(17 | 42)));
}
}