use core::num::NonZeroUsize;
use core::time::Duration;
use std::sync::OnceLock;
pub const PAYS: f64 = 3.0;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DispatchKind {
Team,
InPool,
Outside,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WidthCosts {
ns: [u64; TABLE],
}
const TABLE: usize = 6;
const ANCHORS: [u64; 3] = [2, 4, 6];
const fn to_ns(d: Duration) -> u64 {
let n = d.as_nanos();
if n > u64::MAX as u128 {
u64::MAX
} else {
n as u64
}
}
const fn model_ns(anchors: &[u64; 3], w: u64) -> u64 {
if w <= ANCHORS[0] {
anchors[0]
} else if w >= ANCHORS[2] {
anchors[2]
} else {
let i = (w > ANCHORS[1]) as usize;
let (w0, w1) = (ANCHORS[i], ANCHORS[i + 1]);
let (c0, c1) = (anchors[i] as i128, anchors[i + 1] as i128);
let v = c0 + (c1 - c0) * (w - w0) as i128 / (w1 - w0) as i128;
if v < 0 {
0
} else {
v as u64
}
}
}
impl WidthCosts {
pub const fn new(at_2_4_6: [Duration; 3]) -> Self {
let anchors = [to_ns(at_2_4_6[0]), to_ns(at_2_4_6[1]), to_ns(at_2_4_6[2])];
let mut ns = [0u64; TABLE];
let mut k = 0;
while k < TABLE {
ns[k] = model_ns(&anchors, k as u64 + 1);
k += 1;
}
WidthCosts { ns }
}
pub const fn flat(cost: Duration) -> Self {
WidthCosts::new([cost; 3])
}
#[inline]
pub(crate) fn ns_at(&self, width: NonZeroUsize) -> u64 {
self.ns[(width.get() - 1).min(TABLE - 1)]
}
#[inline]
pub fn at(&self, width: NonZeroUsize) -> Duration {
Duration::from_nanos(self.ns_at(width))
}
#[inline]
pub fn min(&self) -> Duration {
let mut m = self.ns[0];
let mut k = 1;
while k < TABLE {
if self.ns[k] < m {
m = self.ns[k];
}
k += 1;
}
Duration::from_nanos(m)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HostClass {
X86_64Avx512,
X86_64Avx2,
X86_64Scalar,
Aarch64Apple,
Aarch64,
Generic,
}
impl HostClass {
pub fn detect() -> Self {
static CLASS: OnceLock<HostClass> = OnceLock::new();
*CLASS.get_or_init(Self::detect_uncached)
}
#[cfg(target_arch = "x86_64")]
fn detect_uncached() -> Self {
if std::arch::is_x86_feature_detected!("avx512f") {
HostClass::X86_64Avx512
} else if std::arch::is_x86_feature_detected!("avx2")
&& std::arch::is_x86_feature_detected!("fma")
{
HostClass::X86_64Avx2
} else {
HostClass::X86_64Scalar
}
}
#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
fn detect_uncached() -> Self {
HostClass::Aarch64Apple
}
#[cfg(all(target_arch = "aarch64", not(target_os = "macos")))]
fn detect_uncached() -> Self {
HostClass::Aarch64
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
fn detect_uncached() -> Self {
HostClass::Generic
}
pub const fn name(self) -> &'static str {
match self {
HostClass::X86_64Avx512 => "x86_64-avx512",
HostClass::X86_64Avx2 => "x86_64-avx2",
HostClass::X86_64Scalar => "x86_64-scalar",
HostClass::Aarch64Apple => "aarch64-apple",
HostClass::Aarch64 => "aarch64",
HostClass::Generic => "generic",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DispatchCosts {
team: WidthCosts,
in_pool: WidthCosts,
outside: WidthCosts,
}
const fn us_f(x: f64) -> Duration {
Duration::from_nanos((x * 1000.0) as u64)
}
impl DispatchCosts {
pub fn new(team: WidthCosts, in_pool: WidthCosts, outside: WidthCosts) -> Self {
DispatchCosts {
team,
in_pool,
outside,
}
}
pub fn for_class(class: HostClass) -> Self {
match class {
HostClass::X86_64Avx512 => DispatchCosts {
team: WidthCosts::new([us_f(0.6), us_f(0.7), us_f(0.9)]),
in_pool: WidthCosts::new([us_f(3.0), us_f(3.6), us_f(3.5)]),
outside: WidthCosts::new([us_f(9.7), us_f(10.1), us_f(8.7)]),
},
HostClass::Aarch64Apple => DispatchCosts {
team: WidthCosts::new([us_f(3.0), us_f(2.3), us_f(1.4)]),
in_pool: WidthCosts::new([us_f(6.0), us_f(11.0), us_f(5.5)]),
outside: WidthCosts::new([us_f(14.0), us_f(18.0), us_f(19.0)]),
},
HostClass::X86_64Avx2
| HostClass::X86_64Scalar
| HostClass::Aarch64
| HostClass::Generic => DispatchCosts {
team: WidthCosts::flat(us_f(3.0)),
in_pool: WidthCosts::flat(us_f(15.0)),
outside: WidthCosts::flat(us_f(40.0)),
},
}
}
pub fn host_default() -> Self {
Self::for_class(HostClass::detect())
}
#[must_use]
pub fn with(mut self, kind: DispatchKind, costs: WidthCosts) -> Self {
match kind {
DispatchKind::Team => self.team = costs,
DispatchKind::InPool => self.in_pool = costs,
DispatchKind::Outside => self.outside = costs,
}
self
}
pub fn min_cost(&self, kind: DispatchKind) -> Duration {
match kind {
DispatchKind::Team => self.team.min(),
DispatchKind::InPool => self.in_pool.min(),
DispatchKind::Outside => self.outside.min(),
}
}
#[inline]
pub fn cost(&self, kind: DispatchKind, width: NonZeroUsize) -> Duration {
match kind {
DispatchKind::Team => self.team.at(width),
DispatchKind::InPool => self.in_pool.at(width),
DispatchKind::Outside => self.outside.at(width),
}
}
}
impl Default for DispatchCosts {
fn default() -> Self {
DispatchCosts::host_default()
}
}
const PAYS_INT: u128 = 3;
#[inline]
fn pays_ns(est_ns: u64, sections: u32, cost_ns: u64) -> bool {
u128::from(est_ns) >= PAYS_INT * u128::from(sections) * u128::from(cost_ns)
}
pub fn pays_at(estimate: Duration, sections: u32, dispatch_cost: Duration) -> bool {
if dispatch_cost == Duration::MAX {
return false;
}
if sections == 0 {
return true;
}
pays_ns(to_ns(estimate), sections, to_ns(dispatch_cost))
}
pub fn width_for_by(
estimate: Duration,
sections: u32,
budget: NonZeroUsize,
cost: impl Fn(NonZeroUsize) -> Duration,
) -> NonZeroUsize {
let est_ns = to_ns(estimate);
let est = est_ns as f64;
let mut best = NonZeroUsize::MIN;
let mut best_obj = est;
for k in 2..=budget.get() {
let Some(width) = NonZeroUsize::new(k) else {
continue;
};
let d = cost(width);
if d == Duration::MAX {
continue;
}
let d_ns = to_ns(d);
if sections != 0 && !pays_ns(est_ns, sections, d_ns) {
continue;
}
let obj = f64::from(sections) * d_ns as f64 + est / k as f64;
if obj < best_obj {
best = width;
best_obj = obj;
}
}
best
}
#[cfg(test)]
mod tests {
use super::*;
fn nz(n: usize) -> NonZeroUsize {
NonZeroUsize::new(n).unwrap()
}
fn us(n: u64) -> Duration {
Duration::from_micros(n)
}
#[test]
fn anchors_are_returned_exactly() {
let c = DispatchCosts::for_class(HostClass::Aarch64Apple);
assert_eq!(c.cost(DispatchKind::Team, nz(2)), us_f(3.0));
assert_eq!(c.cost(DispatchKind::Team, nz(4)), us_f(2.3));
assert_eq!(c.cost(DispatchKind::Team, nz(6)), us_f(1.4));
assert_eq!(c.cost(DispatchKind::Outside, nz(6)), us_f(19.0));
}
#[test]
fn interpolation_and_extrapolation() {
let c = WidthCosts::new([us(3), us(2), us(1)]);
assert_eq!(c.at(nz(1)), us(3));
assert_eq!(c.at(nz(3)), Duration::from_nanos(2500));
assert_eq!(c.at(nz(5)), Duration::from_nanos(1500));
assert_eq!(c.at(nz(8)), us(1));
assert_eq!(c.at(nz(12)), us(1));
let n = WidthCosts::new([us(1), us(5), us(0)]);
assert_eq!(n.at(nz(5)), Duration::from_nanos(2500));
}
fn old_at(ns: [u64; 3], w: u64) -> u64 {
if w <= 2 {
ns[0]
} else if w >= 6 {
ns[2]
} else {
let i = usize::from(w > 4);
let (w0, w1) = ([2u64, 4, 6][i], [2u64, 4, 6][i + 1]);
let (c0, c1) = (i128::from(ns[i]), i128::from(ns[i + 1]));
(c0 + (c1 - c0) * i128::from(w - w0) / i128::from(w1 - w0)).max(0) as u64
}
}
#[test]
fn table_equals_the_interpolation_for_every_width() {
let tables = [
[3000, 2300, 1400],
[2800, 2200, 1700],
[14000, 18000, 19000],
[9500, 10100, 8800],
[3100, 3600, 3500],
[1000, 5000, 0],
[0, 0, 0],
[7, 3, 11],
];
for ns in tables {
let c = WidthCosts::new(ns.map(Duration::from_nanos));
for w in 1..=64u64 {
assert_eq!(
c.at(nz(w as usize)),
Duration::from_nanos(old_at(ns, w)),
"{ns:?} width {w}"
);
}
}
for d in [
DispatchCosts::for_class(HostClass::Aarch64Apple),
DispatchCosts::for_class(HostClass::X86_64Avx512),
DispatchCosts::for_class(HostClass::Generic),
] {
for kind in [
DispatchKind::Team,
DispatchKind::InPool,
DispatchKind::Outside,
] {
let m = (1..=64).map(|w| d.cost(kind, nz(w))).min().unwrap();
assert_eq!(d.min_cost(kind), m);
}
}
}
#[test]
fn min_is_right_for_a_rising_table_and_a_falling_one() {
let rising = WidthCosts::new([us(2), us(5), us(9)]);
assert_eq!(rising.min(), us(2));
let falling = WidthCosts::new([us(9), us(5), us(2)]);
assert_eq!(falling.min(), us(2));
let dip = WidthCosts::new([us(9), us(1), us(9)]);
assert_eq!(dip.min(), us(1));
assert_eq!(WidthCosts::flat(us(4)).min(), us(4));
}
#[test]
fn pays_matches_the_float_rule() {
assert_eq!(PAYS, PAYS_INT as f64);
for cost in [1u64, 700, 1700, 2800, 19_000] {
for sections in [1u32, 2, 5] {
let edge = 3 * u64::from(sections) * cost;
for est in [edge - 1, edge, edge + 1] {
let f = est as f64 >= PAYS * f64::from(sections) * cost as f64;
assert_eq!(
pays_at(
Duration::from_nanos(est),
sections,
Duration::from_nanos(cost)
),
f
);
}
}
}
}
#[test]
fn pays_is_the_multiple_of_the_cost() {
let d = us(2);
assert!(pays_at(us(6), 1, d));
assert!(!pays_at(Duration::from_nanos(5999), 1, d));
assert!(pays_at(us(12), 2, d));
assert!(!pays_at(us(11), 2, d));
assert!(!pays_at(us(1_000_000), 1, Duration::MAX));
assert!(pays_at(Duration::ZERO, 0, d));
}
#[test]
fn width_for_minimises_the_model() {
let budget = nz(6);
let d = |k: NonZeroUsize| us(2) * k.get() as u32;
assert_eq!(width_for_by(us(200), 1, budget, d).get(), 6);
assert_eq!(width_for_by(us(50), 1, budget, d).get(), 5);
assert_eq!(width_for_by(us(5), 1, budget, d).get(), 1);
assert!(width_for_by(us(200), 4, budget, d).get() < 6);
assert_eq!(
width_for_by(us(1_000_000), 1, budget, |_| Duration::MAX).get(),
1
);
assert_eq!(width_for_by(us(1_000_000), 1, nz(1), d).get(), 1);
}
}