Skip to main content

strided_perm/
host.rs

1//! Host class and the serial copy rate of each class.
2//!
3//! The rates of the strided stack are keyed by the kernel dispatch the machine
4//! takes, not by the CPU model. `HostClass` and `HostClass::detect` are the same
5//! enum and the same detection as in `threadteam` (`threadteam::HostClass`), the
6//! `tcontract` and the `faer` cost tables, and are kept in sync with them by
7//! hand; this crate owns the copy of the strided workspace (`strided-basic`
8//! reuses it for its own table, which sits above this crate).
9
10use std::sync::OnceLock;
11
12/// The class of the machine, keyed by the kernel dispatch it will take.
13///
14/// Kept in sync with `threadteam::HostClass`. Detected once per process by
15/// [`HostClass::detect`].
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub enum HostClass {
18    /// `x86_64` with AVX-512F at run time (`x86_64-avx512`).
19    X86_64Avx512,
20    /// `x86_64` with AVX2 and FMA, without AVX-512F (`x86_64-avx2`).
21    X86_64Avx2,
22    /// Any other `x86_64` (`x86_64-scalar`).
23    X86_64Scalar,
24    /// `aarch64` on macOS (`aarch64-apple`).
25    Aarch64Apple,
26    /// Any other `aarch64` (`aarch64`).
27    Aarch64,
28    /// Everything else (`generic`).
29    Generic,
30}
31
32impl HostClass {
33    /// The class of this host, detected at the first call and cached
34    /// (`OnceLock`; `is_x86_feature_detected!` on x86_64).
35    pub fn detect() -> Self {
36        static CLASS: OnceLock<HostClass> = OnceLock::new();
37        *CLASS.get_or_init(Self::detect_uncached)
38    }
39
40    #[cfg(target_arch = "x86_64")]
41    fn detect_uncached() -> Self {
42        if std::arch::is_x86_feature_detected!("avx512f") {
43            HostClass::X86_64Avx512
44        } else if std::arch::is_x86_feature_detected!("avx2")
45            && std::arch::is_x86_feature_detected!("fma")
46        {
47            HostClass::X86_64Avx2
48        } else {
49            HostClass::X86_64Scalar
50        }
51    }
52
53    #[cfg(all(target_arch = "aarch64", target_os = "macos"))]
54    fn detect_uncached() -> Self {
55        HostClass::Aarch64Apple
56    }
57
58    #[cfg(all(target_arch = "aarch64", not(target_os = "macos")))]
59    fn detect_uncached() -> Self {
60        HostClass::Aarch64
61    }
62
63    #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
64    fn detect_uncached() -> Self {
65        HostClass::Generic
66    }
67
68    /// The class name used in the docs and the tables.
69    pub const fn name(self) -> &'static str {
70        match self {
71            HostClass::X86_64Avx512 => "x86_64-avx512",
72            HostClass::X86_64Avx2 => "x86_64-avx2",
73            HostClass::X86_64Scalar => "x86_64-scalar",
74            HostClass::Aarch64Apple => "aarch64-apple",
75            HostClass::Aarch64 => "aarch64",
76            HostClass::Generic => "generic",
77        }
78    }
79}
80
81/// Serial throughput of a copy in nanoseconds per element, of a host class.
82///
83/// * `x86_64-avx512`: 0.171, the median of `seq` time per element of the strided
84///   `copy` probe at 16384, 32768 and 65536 elements (L2 resident; 0.171, 0.131,
85///   0.201) on an AMD Ryzen AI 9 HX 470 at 2.0 GHz (boost off), `faer-benchmark`
86///   `data/probes/20261026-awake-team-linux/tables-confirm-exec3.md`.
87/// * `aarch64-apple`: 0.090, Apple M5 Max (thresholds.md section 3.2).
88/// * every other class: the `generic` row, the fastest known value (the minimum
89///   over the measured classes), which keeps work serial: 0.090.
90///
91/// A row's rates and the dispatch costs of the same class in `threadteam` come
92/// from the same machine in the same clock state (boost on or off moves both by
93/// about 1.66x and keeps their ratio within a few percent).
94pub const fn copy_ns_per_element_of(class: HostClass) -> f64 {
95    match class {
96        HostClass::X86_64Avx512 => 0.171,
97        HostClass::Aarch64Apple => 0.090,
98        HostClass::X86_64Avx2
99        | HostClass::X86_64Scalar
100        | HostClass::Aarch64
101        | HostClass::Generic => 0.090,
102    }
103}
104
105/// Serial throughput of a copy in nanoseconds per element on this host
106/// ([`copy_ns_per_element_of`] of [`HostClass::detect`]).
107///
108/// The `Copy` class of the strided stack (`strided_basic::execution::KernelClass`
109/// holds the class table; this crate sits below it and owns the one rate it
110/// needs). `copy_into*` multiply it by the element count and ask the token
111/// whether one section pays.
112pub fn copy_ns_per_element() -> f64 {
113    copy_ns_per_element_of(HostClass::detect())
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn rows_per_class() {
122        assert_eq!(copy_ns_per_element_of(HostClass::X86_64Avx512), 0.171);
123        assert_eq!(copy_ns_per_element_of(HostClass::Aarch64Apple), 0.090);
124        for c in [
125            HostClass::X86_64Avx2,
126            HostClass::X86_64Scalar,
127            HostClass::Aarch64,
128            HostClass::Generic,
129        ] {
130            assert_eq!(copy_ns_per_element_of(c), 0.090, "{}", c.name());
131        }
132        for (c, name) in [
133            (HostClass::X86_64Avx512, "x86_64-avx512"),
134            (HostClass::X86_64Avx2, "x86_64-avx2"),
135            (HostClass::X86_64Scalar, "x86_64-scalar"),
136            (HostClass::Aarch64Apple, "aarch64-apple"),
137            (HostClass::Aarch64, "aarch64"),
138            (HostClass::Generic, "generic"),
139        ] {
140            assert_eq!(c.name(), name);
141        }
142        assert_eq!(HostClass::detect(), HostClass::detect());
143        assert_eq!(
144            copy_ns_per_element(),
145            copy_ns_per_element_of(HostClass::detect())
146        );
147    }
148}