#![cfg_attr(coverage_nightly, coverage(off))]
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub struct HardwareClass {
pub cpu_family: CpuFamily,
pub core_count_class: CoreClass,
pub cache_class: CacheClass,
}
impl HardwareClass {
#[must_use]
pub fn from_system() -> Self {
let cpu_count = num_cpus::get();
Self {
cpu_family: CpuFamily::detect(),
core_count_class: CoreClass::from_count(cpu_count),
cache_class: CacheClass::detect(),
}
}
#[must_use]
pub fn similarity(&self, other: &HardwareClass) -> f64 {
let mut score = 0.0;
if self.cpu_family == other.cpu_family {
score += 0.5;
} else if self.cpu_family.compatible_with(&other.cpu_family) {
score += 0.25;
}
let core_distance = self.core_count_class.distance(&other.core_count_class);
score += 0.3 * (1.0 - (core_distance as f64 / 4.0).min(1.0));
let cache_distance = self.cache_class.distance(&other.cache_class);
score += 0.2 * (1.0 - (cache_distance as f64 / 3.0).min(1.0));
score
}
#[must_use]
pub fn performance_factor(&self, baseline: &HardwareClass) -> f64 {
let core_factor = self.core_count_class.speedup() / baseline.core_count_class.speedup();
let cache_factor = 1.0 + (self.cache_class.mb() - baseline.cache_class.mb()) * 0.02;
core_factor * cache_factor
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CpuFamily {
IntelCore,
IntelXeon,
AmdRyzen,
AmdEpyc,
AppleSilicon,
ArmCortex,
Unknown,
}
impl CpuFamily {
#[must_use]
pub fn detect() -> Self {
#[cfg(target_arch = "x86_64")]
{
Self::IntelCore }
#[cfg(target_arch = "aarch64")]
{
Self::AppleSilicon }
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
Self::Unknown
}
}
#[must_use]
pub fn compatible_with(&self, other: &CpuFamily) -> bool {
use CpuFamily::{AmdEpyc, AmdRyzen, AppleSilicon, ArmCortex, IntelCore, IntelXeon};
match (self, other) {
(IntelCore, IntelXeon) | (IntelXeon, IntelCore) => true,
(AmdRyzen, AmdEpyc) | (AmdEpyc, AmdRyzen) => true,
(AppleSilicon, ArmCortex) | (ArmCortex, AppleSilicon) => true,
_ => self == other,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CoreClass {
Single, Dual, Quad, Octa, Many, }
impl CoreClass {
#[must_use]
pub fn from_count(count: usize) -> Self {
match count {
1 => Self::Single,
2 => Self::Dual,
3..=4 => Self::Quad,
5..=8 => Self::Octa,
_ => Self::Many,
}
}
#[must_use]
pub fn distance(&self, other: &CoreClass) -> usize {
use CoreClass::{Dual, Many, Octa, Quad, Single};
let self_idx: usize = match self {
Single => 0,
Dual => 1,
Quad => 2,
Octa => 3,
Many => 4,
};
let other_idx: usize = match other {
Single => 0,
Dual => 1,
Quad => 2,
Octa => 3,
Many => 4,
};
self_idx.abs_diff(other_idx)
}
#[must_use]
pub fn speedup(&self) -> f64 {
match self {
Self::Single => 1.0,
Self::Dual => 1.8,
Self::Quad => 3.2,
Self::Octa => 5.5,
Self::Many => 8.0,
}
}
#[must_use]
pub fn representative_count(&self) -> usize {
match self {
Self::Single => 1,
Self::Dual => 2,
Self::Quad => 4,
Self::Octa => 8,
Self::Many => 16,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CacheClass {
Small, Medium, Large, Huge, }
impl CacheClass {
#[must_use]
pub fn detect() -> Self {
Self::Medium
}
#[must_use]
pub fn distance(&self, other: &CacheClass) -> usize {
use CacheClass::{Huge, Large, Medium, Small};
let self_idx: usize = match self {
Small => 0,
Medium => 1,
Large => 2,
Huge => 3,
};
let other_idx: usize = match other {
Small => 0,
Medium => 1,
Large => 2,
Huge => 3,
};
self_idx.abs_diff(other_idx)
}
#[must_use]
pub fn mb(&self) -> f64 {
match self {
Self::Small => 2.0,
Self::Medium => 6.0,
Self::Large => 12.0,
Self::Huge => 24.0,
}
}
}
impl fmt::Display for HardwareClass {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{:?}/{:?}/{:?}",
self.cpu_family, self.core_count_class, self.cache_class
)
}
}
#[derive(Debug, Clone)]
pub struct HardwareProfile {
pub class: HardwareClass,
pub cpu_name: String,
pub physical_cores: usize,
pub logical_cores: usize,
pub cache_sizes: CacheSizes,
pub memory_gb: f64,
}
#[derive(Debug, Clone)]
pub struct CacheSizes {
pub l1_data_kb: u32,
pub l1_inst_kb: u32,
pub l2_kb: u32,
pub l3_kb: u32,
}
impl HardwareProfile {
#[must_use]
pub fn from_system() -> Self {
let logical_cores = num_cpus::get();
let physical_cores = num_cpus::get_physical();
Self {
class: HardwareClass::from_system(),
cpu_name: "Unknown CPU".to_string(), physical_cores,
logical_cores,
cache_sizes: CacheSizes {
l1_data_kb: 32,
l1_inst_kb: 32,
l2_kb: 256,
l3_kb: 8192,
},
memory_gb: 16.0, }
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_core_class_from_count() {
assert_eq!(CoreClass::from_count(1), CoreClass::Single);
assert_eq!(CoreClass::from_count(2), CoreClass::Dual);
assert_eq!(CoreClass::from_count(3), CoreClass::Quad);
assert_eq!(CoreClass::from_count(4), CoreClass::Quad);
assert_eq!(CoreClass::from_count(5), CoreClass::Octa);
assert_eq!(CoreClass::from_count(8), CoreClass::Octa);
assert_eq!(CoreClass::from_count(16), CoreClass::Many);
}
#[test]
fn test_core_class_distance() {
assert_eq!(CoreClass::Single.distance(&CoreClass::Single), 0);
assert_eq!(CoreClass::Single.distance(&CoreClass::Dual), 1);
assert_eq!(CoreClass::Single.distance(&CoreClass::Many), 4);
assert_eq!(CoreClass::Quad.distance(&CoreClass::Octa), 1);
}
#[test]
fn test_core_class_speedup() {
assert!((CoreClass::Single.speedup() - 1.0).abs() < f64::EPSILON);
assert!((CoreClass::Dual.speedup() - 1.8).abs() < f64::EPSILON);
assert!(CoreClass::Octa.speedup() > CoreClass::Quad.speedup());
}
#[test]
fn test_core_class_representative_count() {
assert_eq!(CoreClass::Single.representative_count(), 1);
assert_eq!(CoreClass::Dual.representative_count(), 2);
assert_eq!(CoreClass::Quad.representative_count(), 4);
assert_eq!(CoreClass::Octa.representative_count(), 8);
assert_eq!(CoreClass::Many.representative_count(), 16);
}
#[test]
fn test_cache_class_distance() {
assert_eq!(CacheClass::Small.distance(&CacheClass::Small), 0);
assert_eq!(CacheClass::Small.distance(&CacheClass::Medium), 1);
assert_eq!(CacheClass::Small.distance(&CacheClass::Huge), 3);
}
#[test]
fn test_cache_class_mb() {
assert!(CacheClass::Small.mb() < CacheClass::Medium.mb());
assert!(CacheClass::Medium.mb() < CacheClass::Large.mb());
assert!(CacheClass::Large.mb() < CacheClass::Huge.mb());
}
#[test]
fn test_cpu_family_compatibility() {
assert!(CpuFamily::IntelCore.compatible_with(&CpuFamily::IntelXeon));
assert!(CpuFamily::IntelXeon.compatible_with(&CpuFamily::IntelCore));
assert!(CpuFamily::AmdRyzen.compatible_with(&CpuFamily::AmdEpyc));
assert!(CpuFamily::AppleSilicon.compatible_with(&CpuFamily::ArmCortex));
assert!(CpuFamily::IntelCore.compatible_with(&CpuFamily::IntelCore));
assert!(!CpuFamily::IntelCore.compatible_with(&CpuFamily::AmdRyzen));
}
#[test]
fn test_hardware_class_similarity() {
let hw1 = HardwareClass {
cpu_family: CpuFamily::IntelCore,
core_count_class: CoreClass::Octa,
cache_class: CacheClass::Medium,
};
let hw2 = HardwareClass {
cpu_family: CpuFamily::IntelCore,
core_count_class: CoreClass::Octa,
cache_class: CacheClass::Medium,
};
assert!((hw1.similarity(&hw2) - 1.0).abs() < f64::EPSILON);
let hw3 = HardwareClass {
cpu_family: CpuFamily::AmdRyzen,
core_count_class: CoreClass::Single,
cache_class: CacheClass::Small,
};
assert!(hw1.similarity(&hw3) < 0.5);
}
#[test]
fn test_hardware_class_performance_factor() {
let baseline = HardwareClass {
cpu_family: CpuFamily::IntelCore,
core_count_class: CoreClass::Quad,
cache_class: CacheClass::Medium,
};
let faster = HardwareClass {
cpu_family: CpuFamily::IntelCore,
core_count_class: CoreClass::Octa,
cache_class: CacheClass::Large,
};
assert!(faster.performance_factor(&baseline) > 1.0);
}
#[test]
fn test_hardware_class_from_system() {
let hw = HardwareClass::from_system();
assert!(hw.core_count_class.speedup() > 0.0);
}
#[test]
fn test_hardware_profile_from_system() {
let profile = HardwareProfile::from_system();
assert!(profile.logical_cores > 0);
assert!(profile.physical_cores > 0);
assert!(profile.logical_cores >= profile.physical_cores);
}
#[test]
fn test_hardware_class_display() {
let hw = HardwareClass {
cpu_family: CpuFamily::IntelCore,
core_count_class: CoreClass::Octa,
cache_class: CacheClass::Medium,
};
let display = format!("{}", hw);
assert!(display.contains("IntelCore"));
assert!(display.contains("Octa"));
assert!(display.contains("Medium"));
}
#[test]
fn test_cache_class_detect() {
let cache = CacheClass::detect();
assert_eq!(cache, CacheClass::Medium);
}
#[test]
fn test_cpu_family_detect() {
let cpu = CpuFamily::detect();
assert!(
cpu != CpuFamily::Unknown
|| cfg!(not(any(target_arch = "x86_64", target_arch = "aarch64")))
);
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn core_class_distance_symmetric(a in 0usize..5, b in 0usize..5) {
let classes = [CoreClass::Single, CoreClass::Dual, CoreClass::Quad, CoreClass::Octa, CoreClass::Many];
let class_a = &classes[a];
let class_b = &classes[b];
prop_assert_eq!(class_a.distance(class_b), class_b.distance(class_a));
}
#[test]
fn cache_class_distance_symmetric(a in 0usize..4, b in 0usize..4) {
let classes = [CacheClass::Small, CacheClass::Medium, CacheClass::Large, CacheClass::Huge];
let class_a = &classes[a];
let class_b = &classes[b];
prop_assert_eq!(class_a.distance(class_b), class_b.distance(class_a));
}
#[test]
fn hardware_similarity_symmetric(core_a in 0usize..5, core_b in 0usize..5) {
let core_classes = [CoreClass::Single, CoreClass::Dual, CoreClass::Quad, CoreClass::Octa, CoreClass::Many];
let hw1 = HardwareClass {
cpu_family: CpuFamily::IntelCore,
core_count_class: core_classes[core_a].clone(),
cache_class: CacheClass::Medium,
};
let hw2 = HardwareClass {
cpu_family: CpuFamily::IntelCore,
core_count_class: core_classes[core_b].clone(),
cache_class: CacheClass::Medium,
};
let sim1 = hw1.similarity(&hw2);
let sim2 = hw2.similarity(&hw1);
prop_assert!((sim1 - sim2).abs() < 0.001);
}
}
}