use std::sync::Arc;
use rayon::prelude::*;
use super::spawn::ThreadPool;
#[derive(Clone)]
pub struct RayonPool {
pool: Option<Arc<rayon::ThreadPool>>,
num_threads: usize,
}
impl Default for RayonPool {
fn default() -> Self {
Self::new()
}
}
impl RayonPool {
#[must_use]
pub fn new() -> Self {
Self {
pool: None,
num_threads: rayon::current_num_threads(),
}
}
#[must_use]
pub fn with_num_threads(num_threads: usize) -> Self {
if num_threads == 0 {
return Self::new();
}
match rayon::ThreadPoolBuilder::new()
.num_threads(num_threads)
.build()
{
Ok(pool) => Self {
pool: Some(Arc::new(pool)),
num_threads,
},
Err(_) => Self::new(),
}
}
#[must_use]
pub fn has_dedicated_pool(&self) -> bool {
self.pool.is_some()
}
}
impl ThreadPool for RayonPool {
fn parallel_for<F>(&self, count: usize, f: F)
where
F: Fn(usize) + Send + Sync,
{
if !super::should_parallelize(count, self.num_threads()) {
for i in 0..count {
f(i);
}
return;
}
match &self.pool {
Some(pool) => pool.install(|| (0..count).into_par_iter().for_each(f)),
None => (0..count).into_par_iter().for_each(f),
}
}
fn num_threads(&self) -> usize {
match &self.pool {
Some(pool) => pool.current_num_threads(),
None => self.num_threads,
}
}
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,
{
match &self.pool {
Some(pool) => pool.install(|| rayon::join(a, b)),
None => rayon::join(a, b),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
use std::sync::Mutex;
use std::thread::ThreadId;
#[test]
fn test_with_num_threads_builds_dedicated_pool() {
let pool = RayonPool::with_num_threads(3);
assert!(pool.has_dedicated_pool());
assert_eq!(pool.num_threads(), 3);
}
#[test]
fn test_zero_threads_uses_ambient_pool() {
let pool = RayonPool::with_num_threads(0);
assert!(!pool.has_dedicated_pool());
assert_eq!(pool.num_threads(), rayon::current_num_threads());
}
#[test]
fn test_parallel_for_installs_into_dedicated_pool() {
let pool = RayonPool::with_num_threads(3);
let observed: Mutex<HashSet<usize>> = Mutex::new(HashSet::new());
pool.parallel_for(64, |_| {
observed
.lock()
.unwrap_or_else(|e| e.into_inner())
.insert(rayon::current_num_threads());
});
assert_eq!(
*observed.lock().unwrap_or_else(|e| e.into_inner()),
HashSet::from([3])
);
}
#[test]
fn test_single_thread_pool_uses_one_os_thread() {
let pool = RayonPool::with_num_threads(1);
assert_eq!(pool.num_threads(), 1);
let seen: Mutex<HashSet<ThreadId>> = Mutex::new(HashSet::new());
pool.parallel_for(64, |_| {
seen.lock()
.unwrap_or_else(|e| e.into_inner())
.insert(std::thread::current().id());
});
assert_eq!(seen.lock().unwrap_or_else(|e| e.into_inner()).len(), 1);
}
#[test]
fn test_dedicated_pool_bounds_distinct_threads() {
let pool = RayonPool::with_num_threads(2);
let seen: Mutex<HashSet<ThreadId>> = Mutex::new(HashSet::new());
pool.parallel_for(64, |i| {
std::thread::yield_now();
let _ = i;
seen.lock()
.unwrap_or_else(|e| e.into_inner())
.insert(std::thread::current().id());
});
let distinct = seen.lock().unwrap_or_else(|e| e.into_inner()).len();
assert!(
(1..=2).contains(&distinct),
"observed {distinct} distinct threads, expected <= 2"
);
}
#[test]
fn test_join_installs_into_dedicated_pool() {
let pool = RayonPool::with_num_threads(2);
let (a, b) = pool.join(rayon::current_num_threads, rayon::current_num_threads);
assert_eq!(a, 2);
assert_eq!(b, 2);
}
#[test]
fn test_nested_parallel_for_stays_within_dedicated_pool() {
let pool = RayonPool::with_num_threads(2);
let inner_observed: Mutex<HashSet<usize>> = Mutex::new(HashSet::new());
pool.parallel_for(32, |_outer| {
(0..4).into_par_iter().for_each(|_inner| {
inner_observed
.lock()
.unwrap_or_else(|e| e.into_inner())
.insert(rayon::current_num_threads());
});
});
let observed = inner_observed.lock().unwrap_or_else(|e| e.into_inner());
assert_eq!(
observed.len(),
1,
"nested work observed multiple thread counts: {observed:?}"
);
assert!(observed.contains(&2));
}
#[test]
fn test_small_workload_falls_back_to_serial_bound_by_config() {
let pool = RayonPool::with_num_threads(8);
let seen: Mutex<Vec<usize>> = Mutex::new(Vec::new());
pool.parallel_for(3, |i| {
seen.lock().unwrap_or_else(|e| e.into_inner()).push(i);
});
let mut got = seen.lock().unwrap_or_else(|e| e.into_inner()).clone();
got.sort_unstable();
assert_eq!(got, vec![0, 1, 2]);
}
}