Skip to main content

orx_parallel/runner/
par_runner.rs

1use crate::parameters::Params;
2use crate::pools::{ThreadPool, max_num_threads_for_computation};
3#[cfg(feature = "std")]
4use crate::runner::runner_variants::WithDiagnostics;
5use orx_concurrent_iter::ConcurrentIter;
6
7/// Controls how parallel computations are divided into chunks and run on a thread pool.
8pub trait ParRunner: Sized + Sync {
9    /// Underlying thread pool.
10    type Pool: ThreadPool;
11
12    /// Parallel computation state that is shared among thread computations.
13    type State: Send + Sync;
14
15    /// State local to a chunk, created when the chunk begins and consumed when it completes.
16    type ChunkState;
17
18    // required
19
20    /// Reference to the underlying thread pool.
21    fn pool(&self) -> &Self::Pool;
22
23    /// Mutable reference to the underlying thread pool.
24    fn pool_mut(&mut self) -> &mut Self::Pool;
25
26    // required - orchestration
27
28    /// Returns Some of the index of the new thread if it is beneficial to spawn a new thread
29    /// for the remaining computation; it returns None otherwise.
30    ///
31    /// Since this method is called sequentially, returned value will always be `Some(spawned)` if
32    /// it returns some.
33    fn do_spawn_new(spawned: usize, state: &Self::State) -> Option<usize>;
34
35    /// Creates an initial state for a new parallel computation.
36    fn new_state(
37        &mut self,
38        params: Params,
39        max_num_threads: usize,
40        size_hint: (usize, Option<usize>),
41    ) -> Self::State;
42
43    /// Adjusts runner state for an input source that is consumed serially.
44    fn configure_for_serialized_input(state: &mut Self::State, size_hint: (usize, Option<usize>));
45
46    /// Called when a worker thread begins processing the computation.
47    fn begin_thread(state: &Self::State, th_idx: usize);
48
49    /// Returns the next chunk size to be pulled from the input with remaining length
50    /// provided by the `size_hint` for the current `state`.
51    fn next_chunk_size(state: &Self::State, size_hint: (usize, Option<usize>)) -> usize;
52
53    /// Creates state for a chunk before its items are processed.
54    fn begin_chunk(th_idx: usize, chunk_size: usize) -> Self::ChunkState;
55
56    /// Called when a chunk completes, with the state returned by [`Self::begin_chunk`].
57    fn complete_chunk(state: &Self::State, chunk_state: Self::ChunkState);
58
59    /// Called when a worker thread finishes processing the computation.
60    fn complete_thread(state: &Self::State, th_idx: usize);
61
62    /// Called after all worker threads finish, consuming the shared computation state.
63    fn complete_computation(state: Self::State);
64
65    // provided
66
67    /// Wraps this runner to print diagnostics about thread usage and chunk sizes (`std` only).
68    #[cfg(feature = "std")]
69    fn with_diagnostics(self) -> WithDiagnostics<Self> {
70        WithDiagnostics::new(self)
71    }
72
73    // provided - helpers
74
75    /// Creates computation state and returns the effective thread limit with that state.
76    ///
77    /// Applies the pool limit and any per-computation limit, then configures the state for
78    /// serialized input when needed.
79    fn nt_state(
80        &mut self,
81        params: Params,
82        is_source_serialized: bool,
83        size_hint: (usize, Option<usize>),
84        computation_max_nt: Option<usize>,
85    ) -> (usize, Self::State) {
86        let max_nt = max_num_threads_for_computation(self.pool(), params, size_hint);
87
88        let max_nt = match computation_max_nt {
89            Some(0) => 1,
90            Some(comp_nt) if comp_nt < max_nt => comp_nt,
91            _ => max_nt,
92        };
93
94        let mut state = self.new_state(params, max_nt, size_hint);
95        if is_source_serialized {
96            Self::configure_for_serialized_input(&mut state, size_hint);
97        }
98        (max_nt, state)
99    }
100
101    /// Exhausts the iterator and completes the active chunk when processing stops early.
102    fn broadcast_stop<I: ConcurrentIter>(
103        iter: &I,
104        state: &Self::State,
105        chunk_state: Self::ChunkState,
106    ) {
107        iter.skip_to_end();
108        Self::complete_chunk(state, chunk_state);
109    }
110}
111
112impl<P: ParRunner> ParRunner for &mut P {
113    type Pool = P::Pool;
114
115    type State = P::State;
116
117    type ChunkState = P::ChunkState;
118
119    fn pool(&self) -> &Self::Pool {
120        <P as ParRunner>::pool(self)
121    }
122
123    fn pool_mut(&mut self) -> &mut Self::Pool {
124        <P as ParRunner>::pool_mut(self)
125    }
126
127    fn do_spawn_new(spawned: usize, state: &Self::State) -> Option<usize> {
128        <P as ParRunner>::do_spawn_new(spawned, state)
129    }
130
131    fn new_state(
132        &mut self,
133        params: Params,
134        max_num_threads: usize,
135        size_hint: (usize, Option<usize>),
136    ) -> Self::State {
137        <P as ParRunner>::new_state(self, params, max_num_threads, size_hint)
138    }
139
140    fn begin_thread(state: &Self::State, th_idx: usize) {
141        <P as ParRunner>::begin_thread(state, th_idx);
142    }
143
144    fn next_chunk_size(state: &Self::State, size_hint: (usize, Option<usize>)) -> usize {
145        <P as ParRunner>::next_chunk_size(state, size_hint)
146    }
147
148    fn begin_chunk(th_idx: usize, chunk_size: usize) -> Self::ChunkState {
149        <P as ParRunner>::begin_chunk(th_idx, chunk_size)
150    }
151
152    fn complete_chunk(state: &Self::State, chunk_state: Self::ChunkState) {
153        <P as ParRunner>::complete_chunk(state, chunk_state);
154    }
155
156    fn complete_thread(state: &Self::State, th_idx: usize) {
157        <P as ParRunner>::complete_thread(state, th_idx);
158    }
159
160    fn complete_computation(state: Self::State) {
161        <P as ParRunner>::complete_computation(state);
162    }
163
164    fn configure_for_serialized_input(state: &mut Self::State, size_hint: (usize, Option<usize>)) {
165        <P as ParRunner>::configure_for_serialized_input(state, size_hint);
166    }
167}