entrenar/efficiency/device/
cpu.rs1use serde::{Deserialize, Serialize};
4
5use super::simd::SimdCapability;
6
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9pub struct CpuInfo {
10 pub cores: u32,
12 pub threads: u32,
14 pub simd: SimdCapability,
16 pub model: String,
18 pub cache_bytes: u64,
20}
21
22impl CpuInfo {
23 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 pub fn with_cache(mut self, cache_bytes: u64) -> Self {
30 self.cache_bytes = cache_bytes;
31 self
32 }
33
34 pub fn detect() -> Self {
36 let threads = std::thread::available_parallelism().map(|n| n.get() as u32).unwrap_or(1);
38
39 let cores = Self::detect_physical_cores().unwrap_or_else(|| threads.max(1));
42 let simd = SimdCapability::detect();
43
44 let model = Self::detect_model();
46
47 Self {
48 cores,
49 threads,
50 simd,
51 model,
52 cache_bytes: 0, }
54 }
55
56 #[cfg(target_os = "linux")]
58 fn detect_physical_cores() -> Option<u32> {
59 std::fs::read_to_string("/proc/cpuinfo").ok().map(|info| {
60 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 info.lines().filter(|line| line.starts_with("processor")).count() as u32
78 } else {
79 core_ids.len() as u32
80 }
81 })
82 }
83
84 #[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 #[cfg(not(any(target_os = "linux", target_os = "macos")))]
97 fn detect_physical_cores() -> Option<u32> {
98 None
99 }
100
101 #[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 pub fn estimated_memory_bandwidth_gbps(&self) -> f64 {
133 40.0 * (f64::from(self.cores) / 8.0).min(2.0)
135 }
136}