moirai_parallel/policy.rs
1//! Compile-time execution policies for data-parallel operations.
2//!
3//! A policy decides *whether* a data-parallel operation runs in parallel. The
4//! types are zero-sized **type-level markers** — the decision is an associated
5//! function, so generic code over `P: ExecutionPolicy` monomorphizes to one
6//! concrete path with no value passed and no dynamic dispatch:
7//!
8//! - [`Sequential`] / [`Parallel`] return a constant, so the unused branch is
9//! eliminated entirely at compile time.
10//! - [`Adaptive`] parallelizes only at or above [`ADAPTIVE_PARALLEL_THRESHOLD`],
11//! a cheap inlined runtime check that routes per workload size (and thus across
12//! the worker threads only when worthwhile).
13//!
14//! Select a policy by type via the [`ParallelSlice`](crate::ParallelSlice) /
15//! [`ParallelSliceMut`](crate::ParallelSliceMut) extension traits
16//! (`slice.par_with::<Parallel>()`) or the `*_with::<P>` functions; the `par_*`
17//! helpers and `slice.par()` use [`Adaptive`] as the unset default.
18
19/// Element count at or above which [`Adaptive`] chooses parallel execution.
20///
21/// Mirrors `moirai-iter`'s `parallel_threshold`: below this, dispatch and join
22/// overhead typically exceeds the benefit of parallelism.
23pub const ADAPTIVE_PARALLEL_THRESHOLD: usize = 1024;
24
25/// Compile-time strategy selector for the data-parallel operations in this crate.
26///
27/// Implemented by zero-sized marker types; used purely as a type parameter so
28/// each operation monomorphizes to a single concrete path.
29pub trait ExecutionPolicy: Send + Sync + 'static {
30 /// Return `true` if an operation over `len` elements should run in parallel.
31 fn parallelize(len: usize) -> bool;
32
33 /// Return `true` if a fixed two-branch operation should run in parallel.
34 #[inline(always)]
35 fn parallelize_pair() -> bool {
36 Self::parallelize(2)
37 }
38}
39
40/// Always run sequentially (single thread, no scheduling).
41#[derive(Debug, Clone, Copy, Default)]
42pub struct Sequential;
43
44/// Always run in parallel on the shared work-stealing pool.
45#[derive(Debug, Clone, Copy, Default)]
46pub struct Parallel;
47
48/// Run in parallel only for inputs at or above [`ADAPTIVE_PARALLEL_THRESHOLD`].
49#[derive(Debug, Clone, Copy, Default)]
50pub struct Adaptive;
51
52impl ExecutionPolicy for Sequential {
53 #[inline(always)]
54 fn parallelize(_len: usize) -> bool {
55 false
56 }
57}
58
59impl ExecutionPolicy for Parallel {
60 #[inline(always)]
61 fn parallelize(_len: usize) -> bool {
62 true
63 }
64
65 #[inline(always)]
66 fn parallelize_pair() -> bool {
67 true
68 }
69}
70
71impl ExecutionPolicy for Adaptive {
72 #[inline(always)]
73 fn parallelize(len: usize) -> bool {
74 len >= ADAPTIVE_PARALLEL_THRESHOLD
75 }
76}
77
78/// Run in parallel only for inputs at or above the custom threshold `N`.
79#[derive(Debug, Clone, Copy, Default)]
80pub struct AdaptiveWithThreshold<const N: usize>;
81
82impl<const N: usize> ExecutionPolicy for AdaptiveWithThreshold<N> {
83 #[inline(always)]
84 fn parallelize(len: usize) -> bool {
85 len >= N
86 }
87}