1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
//! Compile-time execution policies for data-parallel operations.
//!
//! A policy decides *whether* a data-parallel operation runs in parallel. The
//! types are zero-sized **type-level markers** — the decision is an associated
//! function, so generic code over `P: ExecutionPolicy` monomorphizes to one
//! concrete path with no value passed and no dynamic dispatch:
//!
//! - [`Sequential`] / [`Parallel`] return a constant, so the unused branch is
//! eliminated entirely at compile time.
//! - [`Adaptive`] parallelizes only at or above [`ADAPTIVE_PARALLEL_THRESHOLD`],
//! a cheap inlined runtime check that routes per workload size (and thus across
//! the worker threads only when worthwhile).
//!
//! Select a policy by type via the [`ParallelSlice`](crate::ParallelSlice) /
//! [`ParallelSliceMut`](crate::ParallelSliceMut) extension traits
//! (`slice.par_with::<Parallel>()`) or the `*_with::<P>` functions; the `par_*`
//! helpers and `slice.par()` use [`Adaptive`] as the unset default.
/// Element count at or above which [`Adaptive`] chooses parallel execution.
///
/// # This value encodes an assumption about per-element cost
///
/// An element count cannot decide this on its own. Parallel wins once
/// `n * per_element_cost` exceeds the fixed dispatch cost, so the crossover
/// moves with how expensive the body is, and any single count is right for one
/// body weight and wrong for the others.
///
/// Measured on this workstation (best of 30 blocks, `map_reduce`, parallel
/// against the same fold run serially; the dispatch floor is ~11.9 us, which
/// is one task spawned per worker chunk plus the joins):
///
/// ```text
/// body weight crossover parallel/serial at n = 1024
/// one multiply ~21K-32K 20.6x worse
/// sqrt + ln_1p ~8K 4.1x worse
/// 24 chained fused multiply-adds ~512-1024 0.25x (parallel wins)
/// ```
///
/// So 1024 is tuned for an expensive body. A caller folding a cheap expression
/// over 1K-16K elements pays 1.3x to 20.6x for the parallel choice, and the
/// earlier claim here — that below this value dispatch overhead "typically
/// exceeds the benefit" — is the opposite of what happens above it for such a
/// body.
///
/// It is left at 1024 deliberately rather than raised: the stack's own heavy
/// consumers (spherical-harmonic mode loops, for one) fold expensive bodies
/// over exactly the 1K-16K range where raising it would serialize them. The
/// two ways out are re-deriving it against a stated body weight, or shrinking
/// the dispatch floor so the choice matters less; both are tracked rather than
/// guessed at here.
///
/// A caller who knows its body is cheap should select [`Sequential`], and one
/// who knows it is expensive should select [`Parallel`]. `Adaptive` is for
/// callers who know neither, and it cannot be right for both.
pub const ADAPTIVE_PARALLEL_THRESHOLD: usize = 1024;
/// Compile-time strategy selector for the data-parallel operations in this crate.
///
/// Implemented by zero-sized marker types; used purely as a type parameter so
/// each operation monomorphizes to a single concrete path.
/// Always run sequentially (single thread, no scheduling).
;
/// Always run in parallel on the shared work-stealing pool.
;
/// Run in parallel only for inputs at or above [`ADAPTIVE_PARALLEL_THRESHOLD`].
;
/// Run in parallel only for inputs at or above the custom threshold `N`.
;
/// Run in parallel only for an operation that moves at least `N` bytes.
///
/// Byte-reporting operators such as
/// [`for_each_unit_task_mut_with`](crate::for_each_unit_task_mut_with) decide
/// through [`ExecutionPolicy::parallelize_work`]. Entry points that report only
/// an element count are treated as moving one byte per element, a lower bound
/// for any element type, so they lean serial rather than over-schedule.
;