use std::sync::OnceLock;
#[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",
}
}
}
pub const fn ns_per_flop_of(class: HostClass) -> f64 {
match class {
HostClass::X86_64Avx512 => 0.032,
HostClass::Aarch64Apple
| HostClass::X86_64Avx2
| HostClass::X86_64Scalar
| HostClass::Aarch64
| HostClass::Generic => 0.0185,
}
}
pub const fn muladds_per_ns_of(class: HostClass) -> f64 {
match class {
HostClass::X86_64Avx512 => 15.6,
HostClass::Aarch64Apple
| HostClass::X86_64Avx2
| HostClass::X86_64Scalar
| HostClass::Aarch64
| HostClass::Generic => 27.0,
}
}
pub fn ns_per_flop() -> f64 {
ns_per_flop_of(HostClass::detect())
}
pub fn muladds_per_ns() -> f64 {
muladds_per_ns_of(HostClass::detect())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_two_rates_of_a_class_agree() {
for c in [
HostClass::X86_64Avx512,
HostClass::X86_64Avx2,
HostClass::X86_64Scalar,
HostClass::Aarch64Apple,
HostClass::Aarch64,
HostClass::Generic,
] {
let exact = 1.0 / ns_per_flop_of(c) / 2.0;
assert!(
(muladds_per_ns_of(c) / exact - 1.0).abs() < 0.01,
"{}",
c.name()
);
}
}
#[test]
fn rows_per_class() {
assert_eq!(ns_per_flop_of(HostClass::X86_64Avx512), 0.032);
assert_eq!(muladds_per_ns_of(HostClass::X86_64Avx512), 15.6);
for c in [
HostClass::Aarch64Apple,
HostClass::X86_64Avx2,
HostClass::X86_64Scalar,
HostClass::Aarch64,
HostClass::Generic,
] {
assert_eq!(ns_per_flop_of(c), 0.0185, "{}", c.name());
assert_eq!(muladds_per_ns_of(c), 27.0, "{}", c.name());
}
let g = HostClass::Generic;
assert!(ns_per_flop_of(g) <= ns_per_flop_of(HostClass::X86_64Avx512));
assert!(muladds_per_ns_of(g) >= muladds_per_ns_of(HostClass::X86_64Avx512));
assert_eq!(HostClass::detect(), HostClass::detect());
assert_eq!(ns_per_flop(), ns_per_flop_of(HostClass::detect()));
assert_eq!(muladds_per_ns(), muladds_per_ns_of(HostClass::detect()));
}
}