1use std::sync::{Mutex, OnceLock};
2
3use thiserror::Error;
4
5pub trait ChunkSource {
6 fn run_next(&self) -> bool;
7}
8
9pub trait Executor: Send + Sync {
10 fn parallelism(&self) -> usize;
11
12 fn for_each_chunk(&self, workers: usize, chunks: &(dyn ChunkSource + Sync));
14}
15
16pub struct Sequential;
17
18impl Executor for Sequential {
19 fn parallelism(&self) -> usize {
20 1
21 }
22
23 fn for_each_chunk(&self, _workers: usize, chunks: &(dyn ChunkSource + Sync)) {
24 while chunks.run_next() {}
25 }
26}
27
28#[derive(Debug, Error)]
29#[error("a parallel executor is already installed")]
30pub struct AlreadyInstalled;
31
32static INSTALLED: OnceLock<Box<dyn Executor>> = OnceLock::new();
33
34static SEQUENTIAL: Sequential = Sequential;
35
36pub fn install(executor: Box<dyn Executor>) -> Result<(), AlreadyInstalled> {
38 INSTALLED.set(executor).map_err(|_| AlreadyInstalled)
39}
40
41pub fn executor() -> &'static dyn Executor {
42 match INSTALLED.get() {
43 Some(installed) => installed.as_ref(),
44 None => &SEQUENTIAL,
45 }
46}
47
48struct Chunks<'a, T, F> {
49 rest: Mutex<&'a mut [T]>,
50 chunk: usize,
51 task: F,
52}
53
54impl<T: Send, F: Fn(&mut [T]) + Sync> ChunkSource for Chunks<'_, T, F> {
55 fn run_next(&self) -> bool {
56 let part = {
57 let Ok(mut rest) = self.rest.lock() else {
58 return false;
59 };
60 if rest.is_empty() {
61 return false;
62 }
63 let take = rest.len().min(self.chunk);
64 let (head, tail) = std::mem::take(&mut *rest).split_at_mut(take);
65 *rest = tail;
66 head
67 };
68 (self.task)(part);
69 true
70 }
71}
72
73pub fn for_each_chunk<T: Send>(data: &mut [T], chunk: usize, task: impl Fn(&mut [T]) + Sync) {
75 let chunk = chunk.max(1);
76 let executor = executor();
77 let workers = executor
78 .parallelism()
79 .min(data.len().div_ceil(chunk))
80 .max(1);
81 if workers == 1 {
82 for part in data.chunks_mut(chunk) {
83 task(part);
84 }
85 return;
86 }
87 let chunks = Chunks {
88 rest: Mutex::new(data),
89 chunk,
90 task,
91 };
92 executor.for_each_chunk(workers, &chunks);
93}