use std::num::NonZeroUsize;
use std::panic::{AssertUnwindSafe, catch_unwind};
use crate::inference::InferenceError;
pub(crate) struct CpuWorkers<W> {
workspaces: Vec<W>,
budget: NonZeroUsize,
}
impl<W: Send> CpuWorkers<W> {
pub(crate) fn new(first: W) -> Self {
let host = std::thread::available_parallelism().unwrap_or(NonZeroUsize::MIN);
Self {
workspaces: vec![first],
budget: host.min(NonZeroUsize::new(4).unwrap_or(NonZeroUsize::MIN)),
}
}
pub(crate) fn first(&mut self) -> &mut W {
&mut self.workspaces[0]
}
pub(crate) fn map<T: Sync, O: Send>(
&mut self,
inputs: &[T],
init: impl Fn() -> W,
execute: impl Fn(&mut W, &T) -> Result<O, InferenceError> + Sync,
) -> Result<Vec<O>, InferenceError> {
if inputs.is_empty() {
return Ok(Vec::new());
}
let count = inputs.len().min(self.budget.get());
while self.workspaces.len() < count {
self.workspaces.push(init());
}
let run = |workspace: &mut W, chunk: &[T]| {
chunk
.iter()
.map(|input| execute(workspace, input))
.collect::<Result<Vec<_>, _>>()
};
let results = if count == 1 {
vec![catch_unwind(AssertUnwindSafe(|| run(self.first(), inputs)))]
} else {
std::thread::scope(|scope| {
let mut remaining = inputs;
let mut handles = Vec::with_capacity(count);
for (index, workspace) in self.workspaces[..count].iter_mut().enumerate() {
let length = inputs.len() / count + usize::from(index < inputs.len() % count);
let (chunk, rest) = remaining.split_at(length);
remaining = rest;
let run = &run;
handles.push(scope.spawn(move || run(workspace, chunk)));
}
handles
.into_iter()
.map(|handle| handle.join())
.collect::<Vec<_>>()
})
};
let mut outcomes = Vec::with_capacity(count);
for (index, result) in results.into_iter().enumerate() {
let outcome = match result {
Ok(outcome) => outcome,
Err(_) => {
self.workspaces[index] = init();
Err(worker_panic())
}
};
outcomes.push(outcome);
}
let chunks = outcomes.into_iter().collect::<Result<Vec<_>, _>>()?;
Ok(chunks.into_iter().flatten().collect())
}
}
fn worker_panic() -> InferenceError {
InferenceError::WorkerPanic {
worker: "native CPU",
}
}
#[cfg(test)]
mod tests;
#[cfg(test)]
pub(crate) mod test_support {
use super::{CpuWorkers, NonZeroUsize};
pub(crate) fn with_budget<W>(first: W, budget: usize) -> CpuWorkers<W> {
CpuWorkers {
workspaces: vec![first],
budget: NonZeroUsize::new(budget).expect("nonzero test budget"),
}
}
pub(crate) fn count<W>(workers: &CpuWorkers<W>) -> usize {
workers.workspaces.len()
}
}