use std::time::Instant;
pub const INPUT_SETS: usize = 8;
pub const RUNS: usize = 9;
pub const MIN_BATCH_NS: u128 = 50_000;
pub fn time_batch(f: &mut dyn FnMut(), iters: u64) -> f64 {
let start = Instant::now();
for _ in 0..iters {
f();
}
start.elapsed().as_nanos() as f64 / iters as f64
}
pub fn calibrate(f: &mut dyn FnMut()) -> u64 {
let mut iters = 1u64;
loop {
let start = Instant::now();
for _ in 0..iters {
f();
}
let ns = start.elapsed().as_nanos();
if ns >= MIN_BATCH_NS {
return iters;
}
iters = if ns == 0 { iters * 100 } else { iters * 2 };
}
}
pub fn interleaved_min_pair(fa: &mut dyn FnMut(), fb: &mut dyn FnMut()) -> (f64, f64) {
let ia = calibrate(fa);
let ib = calibrate(fb);
let (mut best_a, mut best_b) = (f64::INFINITY, f64::INFINITY);
for _ in 0..RUNS {
best_a = best_a.min(time_batch(fa, ia));
best_b = best_b.min(time_batch(fb, ib));
}
(best_a, best_b)
}
pub fn analyze(dat: &[(usize, f64)]) -> Option<usize> {
let mut best_i = 0;
let mut best_badness = f64::INFINITY;
for i in 0..=dat.len() {
let mut badness = 0.0;
for (j, &(_, d)) in dat.iter().enumerate() {
if j < i {
if d < 0.0 {
badness -= d;
}
} else if d > 0.0 {
badness += d;
}
}
if badness < best_badness {
best_badness = badness;
best_i = i;
}
}
(best_i < dat.len()).then(|| dat[best_i].0)
}
#[allow(clippy::print_stdout)]
pub fn find_crossover_spec(
threshold_name: &str,
threshold_type: &str,
lower_name: &str,
upper_name: &str,
min_size: usize,
max_size: usize,
measure: &dyn Fn(usize) -> Option<(f64, f64)>,
) {
let mut dat = Vec::new();
let mut since_change = 0;
let mut consecutive_upper_wins = 0;
let mut last_thresh = None;
let mut last_size = min_size;
let mut size = min_size as f64;
println!("tuning {threshold_name} ({lower_name} -> {upper_name})");
while (size as usize) < max_size {
let n = size as usize;
size = f64::max(size * 1.05, size + 1.0);
let Some((tl, tu)) = measure(n) else {
continue;
};
let d = if tu >= tl {
(tu - tl) / tu
} else {
(tu - tl) / tl
};
dat.push((n, d));
let thresh = analyze(&dat);
println!(
" size {n:>6} {lower_name} {tl:>10.1}ns {upper_name} {tu:>10.1}ns d {d:>7.4} \
-> {}",
thresh.map_or_else(|| "-".to_string(), |t| t.to_string()),
);
consecutive_upper_wins = if d < 0.0 {
consecutive_upper_wins + 1
} else {
0
};
if consecutive_upper_wins >= 3 && tl >= tu * 1.2 {
break;
}
if thresh == last_thresh {
since_change += 1;
let glued = dat.iter().rev().take(10).any(|&(_, d)| d.abs() < 0.02);
let mean_d = |w: &[(usize, f64)]| w.iter().map(|&(_, d)| d).sum::<f64>() / 10.0;
let closing = dat.len() >= 20
&& mean_d(&dat[dat.len() - 10..])
< mean_d(&dat[dat.len() - 20..dat.len() - 10]) - 0.01;
if since_change > 40 && !glued && !closing {
break;
}
} else {
since_change = 0;
last_thresh = thresh;
}
last_size = n;
}
match analyze(&dat) {
None => println!(
" {threshold_name}: upper algorithm never wins below {last_size} (scan limit \
{max_size})"
),
Some(t) => println!("pub(crate) const {threshold_name}: {threshold_type} = {t};"),
}
}