#[derive(Debug, Clone, Copy)]
pub struct SpeculationObservation {
pub baseline_dispatches: u32,
pub baseline_mean_ns: u64,
pub speculative_dispatches: u32,
pub speculative_mean_ns: u64,
pub side_compile_cost_ns: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpeculationVerdict {
Adopt,
Reject,
KeepRacing,
}
impl std::fmt::Display for SpeculationVerdict {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Adopt => f.write_str("adopt"),
Self::Reject => f.write_str("reject"),
Self::KeepRacing => f.write_str("keep-racing"),
}
}
}
pub const MIN_DISPATCHES_FOR_VERDICT: u32 = 8;
pub const MIN_ADOPT_SAVINGS_BPS: u64 = 1500;
#[must_use]
pub fn decide_speculation(obs: SpeculationObservation) -> SpeculationVerdict {
if obs.baseline_dispatches < MIN_DISPATCHES_FOR_VERDICT
|| obs.speculative_dispatches < MIN_DISPATCHES_FOR_VERDICT
{
return SpeculationVerdict::KeepRacing;
}
if obs.baseline_mean_ns == 0 {
return SpeculationVerdict::KeepRacing;
}
let amortized_overhead_ns = obs
.side_compile_cost_ns
.checked_div(u64::from(obs.speculative_dispatches.max(1)))
.unwrap_or(u64::MAX);
let effective_speculative_ns = obs
.speculative_mean_ns
.saturating_add(amortized_overhead_ns);
if effective_speculative_ns >= obs.baseline_mean_ns {
return SpeculationVerdict::Reject;
}
let savings_ns = obs.baseline_mean_ns - effective_speculative_ns;
let savings_bps = savings_ns
.saturating_mul(10_000)
.checked_div(obs.baseline_mean_ns)
.unwrap_or(0);
if savings_bps >= MIN_ADOPT_SAVINGS_BPS {
SpeculationVerdict::Adopt
} else {
SpeculationVerdict::KeepRacing
}
}
#[cfg(test)]
mod tests {
use super::*;
fn obs(b_n: u32, b_ns: u64, s_n: u32, s_ns: u64, sc_ns: u64) -> SpeculationObservation {
SpeculationObservation {
baseline_dispatches: b_n,
baseline_mean_ns: b_ns,
speculative_dispatches: s_n,
speculative_mean_ns: s_ns,
side_compile_cost_ns: sc_ns,
}
}
#[test]
fn under_threshold_keeps_racing() {
let v = decide_speculation(obs(3, 100_000, 100, 50_000, 0));
assert_eq!(v, SpeculationVerdict::KeepRacing);
}
#[test]
fn speculative_clearly_faster_adopts() {
let v = decide_speculation(obs(50, 100_000, 50, 50_000, 0));
assert_eq!(v, SpeculationVerdict::Adopt);
}
#[test]
fn speculative_slower_rejects() {
let v = decide_speculation(obs(50, 50_000, 50, 100_000, 0));
assert_eq!(v, SpeculationVerdict::Reject);
}
#[test]
fn speculative_marginally_faster_keeps_racing() {
let v = decide_speculation(obs(50, 100_000, 50, 95_000, 0));
assert_eq!(v, SpeculationVerdict::KeepRacing);
}
#[test]
fn side_compile_cost_amortizes_into_decision() {
let v = decide_speculation(obs(50, 100_000, 50, 50_000, 1_000_000));
assert_eq!(v, SpeculationVerdict::Adopt);
}
#[test]
fn side_compile_cost_can_dominate_early() {
let v = decide_speculation(obs(50, 100_000, 8, 50_000, 1_000_000));
assert_eq!(v, SpeculationVerdict::Reject);
}
#[test]
fn zero_baseline_keeps_racing_rather_than_dividing_by_zero() {
let v = decide_speculation(obs(50, 0, 50, 50_000, 0));
assert_eq!(v, SpeculationVerdict::KeepRacing);
}
#[test]
fn extreme_inputs_do_not_panic() {
let _ = decide_speculation(obs(u32::MAX, u64::MAX, u32::MAX, u64::MAX, u64::MAX));
let _ = decide_speculation(obs(u32::MAX, 1, u32::MAX, u64::MAX, 0));
}
#[test]
fn verdict_displays_stable_strings() {
assert_eq!(format!("{}", SpeculationVerdict::Adopt), "adopt");
assert_eq!(format!("{}", SpeculationVerdict::Reject), "reject");
assert_eq!(format!("{}", SpeculationVerdict::KeepRacing), "keep-racing");
}
}