Skip to main content

entrenar/efficiency/device/
cpu.rs

1//! CPU information and detection.
2
3use serde::{Deserialize, Serialize};
4
5use super::simd::SimdCapability;
6
7/// CPU information
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9pub struct CpuInfo {
10    /// Number of physical cores
11    pub cores: u32,
12    /// Number of logical threads (with hyperthreading)
13    pub threads: u32,
14    /// SIMD capability
15    pub simd: SimdCapability,
16    /// CPU model name
17    pub model: String,
18    /// Cache size in bytes (L3 or total)
19    pub cache_bytes: u64,
20}
21
22impl CpuInfo {
23    /// Create new CPU info
24    pub fn new(cores: u32, threads: u32, simd: SimdCapability, model: impl Into<String>) -> Self {
25        Self { cores, threads, simd, model: model.into(), cache_bytes: 0 }
26    }
27
28    /// Set cache size
29    pub fn with_cache(mut self, cache_bytes: u64) -> Self {
30        self.cache_bytes = cache_bytes;
31        self
32    }
33
34    /// Detect current CPU information
35    pub fn detect() -> Self {
36        // Get logical CPU count using standard library
37        let threads = std::thread::available_parallelism().map(|n| n.get() as u32).unwrap_or(1);
38
39        // Estimate physical cores (assumes hyperthreading with 2 threads per core)
40        // This is a heuristic - for accurate counts would need platform-specific APIs
41        let cores = Self::detect_physical_cores().unwrap_or_else(|| threads.max(1));
42        let simd = SimdCapability::detect();
43
44        // Try to get CPU model name
45        let model = Self::detect_model();
46
47        Self {
48            cores,
49            threads,
50            simd,
51            model,
52            cache_bytes: 0, // Would need platform-specific APIs
53        }
54    }
55
56    /// Detect physical core count (Linux-specific)
57    #[cfg(target_os = "linux")]
58    fn detect_physical_cores() -> Option<u32> {
59        std::fs::read_to_string("/proc/cpuinfo").ok().map(|info| {
60            // Count unique core IDs
61            let mut core_ids: std::collections::HashSet<String> = std::collections::HashSet::new();
62            let mut current_physical_id = String::new();
63
64            for line in info.lines() {
65                if line.starts_with("physical id") {
66                    current_physical_id =
67                        line.split(':').nth(1).map(|s| s.trim().to_string()).unwrap_or_default();
68                } else if line.starts_with("core id") {
69                    let core_id =
70                        line.split(':').nth(1).map(|s| s.trim().to_string()).unwrap_or_default();
71                    core_ids.insert(format!("{current_physical_id}-{core_id}"));
72                }
73            }
74
75            if core_ids.is_empty() {
76                // Fallback: count processor entries
77                info.lines().filter(|line| line.starts_with("processor")).count() as u32
78            } else {
79                core_ids.len() as u32
80            }
81        })
82    }
83
84    /// Detect physical core count (macOS-specific)
85    #[cfg(target_os = "macos")]
86    fn detect_physical_cores() -> Option<u32> {
87        std::process::Command::new("sysctl")
88            .args(["-n", "hw.physicalcpu"])
89            .output()
90            .ok()
91            .and_then(|output| String::from_utf8(output.stdout).ok())
92            .and_then(|s| s.trim().parse().ok())
93    }
94
95    /// Detect physical core count (fallback)
96    #[cfg(not(any(target_os = "linux", target_os = "macos")))]
97    fn detect_physical_cores() -> Option<u32> {
98        None
99    }
100
101    /// Detect CPU model name
102    #[cfg(target_os = "linux")]
103    fn detect_model() -> String {
104        std::fs::read_to_string("/proc/cpuinfo")
105            .ok()
106            .and_then(|info| {
107                info.lines()
108                    .find(|line| line.starts_with("model name"))
109                    .and_then(|line| line.split(':').nth(1))
110                    .map(|s| s.trim().to_string())
111            })
112            .unwrap_or_else(|| "Unknown CPU".to_string())
113    }
114
115    #[cfg(target_os = "macos")]
116    fn detect_model() -> String {
117        std::process::Command::new("sysctl")
118            .args(["-n", "machdep.cpu.brand_string"])
119            .output()
120            .ok()
121            .and_then(|output| String::from_utf8(output.stdout).ok())
122            .map(|s| s.trim().to_string())
123            .unwrap_or_else(|| "Unknown CPU".to_string())
124    }
125
126    #[cfg(not(any(target_os = "linux", target_os = "macos")))]
127    fn detect_model() -> String {
128        "Unknown CPU".to_string()
129    }
130
131    /// Estimate memory bandwidth based on core count (rough approximation)
132    pub fn estimated_memory_bandwidth_gbps(&self) -> f64 {
133        // Rough estimate: ~20 GB/s per channel, assume 2 channels for desktop
134        40.0 * (f64::from(self.cores) / 8.0).min(2.0)
135    }
136}