use std::num::NonZeroUsize;
use crate::{ExecError, Pool};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Par {
Seq,
Threads(NonZeroUsize),
}
impl Par {
pub fn threads(self) -> usize {
match self {
Par::Seq => 1,
Par::Threads(n) => n.get(),
}
}
}
#[derive(Clone, Copy)]
#[non_exhaustive]
pub enum Exec<'a> {
Serial,
SerialWithWorkspace(&'a dyn crate::WorkspaceProvider),
Rayon {
pool: &'a Pool<'a>,
budget: NonZeroUsize,
},
}
impl std::fmt::Debug for Exec<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Exec::Serial => f.write_str("Serial"),
Exec::SerialWithWorkspace(_) => f.write_str("SerialWithWorkspace(..)"),
Exec::Rayon { pool, budget } => f
.debug_struct("Rayon")
.field("pool", pool)
.field("budget", budget)
.finish(),
}
}
}
impl<'a> Exec<'a> {
pub const fn serial() -> Exec<'static> {
Exec::Serial
}
pub fn serial_with_workspace(workspace: &'a dyn crate::WorkspaceProvider) -> Self {
Exec::SerialWithWorkspace(workspace)
}
pub fn rayon(pool: &'a Pool<'a>) -> Self {
let budget = NonZeroUsize::new(pool.size()).unwrap_or(NonZeroUsize::MIN);
Exec::Rayon { pool, budget }
}
pub fn with_budget(self, max_threads: usize) -> Result<Self, ExecError> {
let max = NonZeroUsize::new(max_threads).ok_or(ExecError::ZeroBudget)?;
Ok(match self {
Exec::Serial | Exec::SerialWithWorkspace(_) => self,
Exec::Rayon { pool, .. } => {
let cap = NonZeroUsize::new(pool.size()).unwrap_or(NonZeroUsize::MIN);
Exec::Rayon {
pool,
budget: max.min(cap),
}
}
})
}
pub fn budget(&self) -> usize {
match self {
Exec::Serial | Exec::SerialWithWorkspace(_) => 1,
Exec::Rayon { budget, .. } => budget.get(),
}
}
pub fn workspace(&self) -> Option<&'a dyn crate::WorkspaceProvider> {
match self {
Exec::Serial => None,
Exec::SerialWithWorkspace(workspace) => Some(*workspace),
Exec::Rayon { pool, .. } => Some(pool.workspace()),
}
}
pub fn is_worker(&self) -> bool {
match self {
Exec::Serial | Exec::SerialWithWorkspace(_) => false,
Exec::Rayon { pool, .. } => pool.is_worker(),
}
}
fn width(&self, k: usize) -> usize {
k.clamp(1, self.budget())
}
pub fn install<R: Send>(&self, k: usize, op: impl FnOnce(Par) -> R + Send) -> R {
let k = self.width(k);
match (self, NonZeroUsize::new(k)) {
(Exec::Rayon { pool, .. }, Some(n)) if k > 1 => {
if pool.is_worker() {
pool.count_inline();
op(Par::Threads(n))
} else {
pool.count_entry();
pool.tp().install(|| op(Par::Threads(n)))
}
}
_ => op(Par::Seq),
}
}
pub fn for_each_partition(&self, k: usize, f: &(dyn Fn(usize) + Sync)) {
if k == 0 {
return;
}
let lanes = self.width(k);
if lanes == 1 {
(0..k).for_each(f);
return;
}
self.install(lanes, |_| {
rayon::scope(|s| {
for lane in 0..lanes {
s.spawn(move |_| {
let lo = lane * k / lanes;
let hi = (lane + 1) * k / lanes;
(lo..hi).for_each(f);
});
}
})
});
}
pub fn broadcast(&self, width: usize, f: &(dyn Fn(usize) + Sync)) -> Result<(), ExecError> {
if width == 0 {
return Ok(());
}
match self {
Exec::Serial | Exec::SerialWithWorkspace(_) => {
if width == 1 {
f(0);
Ok(())
} else {
Err(ExecError::Unavailable)
}
}
Exec::Rayon { pool, budget } => {
let size = pool.size();
if width > size {
return Err(ExecError::WidthExceedsPool { width, pool: size });
}
if width > budget.get() {
return Err(ExecError::WidthExceedsBudget {
width,
budget: budget.get(),
});
}
if pool.is_worker() {
return Err(ExecError::Unavailable);
}
if width == 1 {
f(0);
return Ok(());
}
let _guard = pool
.spmd
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
pool.count_broadcast();
pool.tp().broadcast(|ctx| {
let t = ctx.index();
if t < width {
f(t)
}
});
Ok(())
}
}
}
pub fn width_for(&self, serial_ns: f64, policy: &crate::WidthPolicy) -> usize {
let budget = self.budget();
if budget == 1 || serial_ns.is_nan() || serial_ns < policy.serial_below_ns {
return 1;
}
let cost = |k: usize| {
if k == 1 {
serial_ns
} else {
policy.entry_base_ns + policy.entry_per_thread_ns * k as f64 + serial_ns / k as f64
}
};
(1..=budget)
.min_by(|&a, &b| cost(a).total_cmp(&cost(b)))
.unwrap_or(1)
}
}