Skip to main content

tcontract_exec/
width.rs

1/// Cost model for choosing a barrier-free partition width from the work.
2///
3/// `T_par(k) = entry_base_ns + entry_per_thread_ns * k + serial_ns / k` for
4/// `k > 1`; work below `serial_below_ns` always runs serially. The defaults
5/// are provisional values from `experiments/rayon-entry` (M5 Max,
6/// 2026-09-29); kernels replace them with their own measured policy.
7///
8/// # Examples
9///
10/// ```
11/// let p = tcontract_exec::WidthPolicy::default();
12/// assert_eq!(p.serial_below_ns, 50_000.0);
13/// ```
14#[derive(Clone, Copy, Debug, PartialEq)]
15pub struct WidthPolicy {
16    /// Estimated serial time below which work never leaves the caller.
17    pub serial_below_ns: f64,
18    /// Fixed part of the pool entry cost.
19    pub entry_base_ns: f64,
20    /// Entry cost added per participating worker.
21    pub entry_per_thread_ns: f64,
22}
23
24impl Default for WidthPolicy {
25    fn default() -> Self {
26        Self {
27            serial_below_ns: 50_000.0,
28            entry_base_ns: 5_000.0,
29            entry_per_thread_ns: 5_000.0,
30        }
31    }
32}
33
34/// Partition width for work estimated at `serial_ns` on one thread,
35/// minimizing the [`WidthPolicy`] cost model within `budget` threads.
36///
37/// This is the one body of the width rule: [`Exec::width_for`](crate::Exec::width_for)
38/// and the pure batch policy module both call it, so a plan-selected policy and a
39/// route decision cannot drift.
40pub(crate) fn width_for(serial_ns: f64, policy: &WidthPolicy, budget: usize) -> usize {
41    // NaN or small work stays serial.
42    if budget == 1 || serial_ns.is_nan() || serial_ns < policy.serial_below_ns {
43        return 1;
44    }
45    let cost = |k: usize| {
46        if k == 1 {
47            serial_ns
48        } else {
49            policy.entry_base_ns + policy.entry_per_thread_ns * k as f64 + serial_ns / k as f64
50        }
51    };
52    (1..=budget)
53        .min_by(|&a, &b| cost(a).total_cmp(&cost(b)))
54        .unwrap_or(1)
55}