scirs2_core/resource/
cpu.rs

1//! # CPU Detection and Capabilities
2//!
3//! This module provides CPU detection and capability assessment for
4//! optimizing computational workloads.
5
6use crate::error::{CoreError, CoreResult};
7#[cfg(target_os = "linux")]
8use std::fs;
9
10/// CPU information and capabilities
11#[derive(Debug, Clone)]
12pub struct CpuInfo {
13    /// CPU model name
14    pub model: String,
15    /// CPU vendor
16    pub vendor: String,
17    /// Number of physical cores
18    pub physical_cores: usize,
19    /// Number of logical cores (with hyperthreading)
20    pub logical_cores: usize,
21    /// Base frequency in GHz
22    pub base_frequency_ghz: f64,
23    /// Maximum frequency in GHz
24    pub max_frequency_ghz: f64,
25    /// L1 cache size in KB
26    pub cache_l1_kb: usize,
27    /// L2 cache size in KB
28    pub cache_l2_kb: usize,
29    /// L3 cache size in KB
30    pub cache_l3_kb: usize,
31    /// SIMD instruction set support
32    pub simd_capabilities: SimdCapabilities,
33    /// CPU architecture
34    pub architecture: CpuArchitecture,
35    /// Additional features
36    pub features: Vec<String>,
37}
38
39impl Default for CpuInfo {
40    fn default() -> Self {
41        Self {
42            model: "Unknown CPU".to_string(),
43            vendor: "Unknown".to_string(),
44            physical_cores: 1,
45            logical_cores: 1,
46            base_frequency_ghz: 1.0,
47            max_frequency_ghz: 1.0,
48            cache_l1_kb: 32,
49            cache_l2_kb: 256,
50            cache_l3_kb: 1024,
51            simd_capabilities: SimdCapabilities::default(),
52            architecture: CpuArchitecture::Unknown,
53            features: Vec::new(),
54        }
55    }
56}
57
58impl CpuInfo {
59    /// Detect CPU information
60    pub fn detect() -> CoreResult<Self> {
61        #[cfg(target_os = "linux")]
62        return Self::detect_linux();
63
64        #[cfg(target_os = "windows")]
65        return Self::detect_windows();
66
67        #[cfg(target_os = "macos")]
68        return Self::detect_macos();
69
70        #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
71        return Ok(Self::default());
72
73        fn parse_cache_size(sizestr: &str) -> Option<u64> {
74            let parts: Vec<&str> = sizestr.split_whitespace().collect();
75            if parts.is_empty() {
76                return None;
77            }
78
79            let number = parts[0].parse::<u64>().ok()?;
80
81            // Convert to bytes
82            if parts.len() > 1 {
83                match parts[1] {
84                    "K" | "KB" => Some(number * 1024),
85                    "M" | "MB" => Some(number * 1024 * 1024),
86                    "G" | "GB" => Some(number * 1024 * 1024 * 1024),
87                    _ => Some(number),
88                }
89            } else {
90                Some(number)
91            }
92        }
93    }
94
95    /// Detect CPU information on Linux
96    #[cfg(target_os = "linux")]
97    fn detect_linux() -> CoreResult<Self> {
98        let cpuinfo = fs::read_to_string("/proc/cpuinfo").map_err(|e| {
99            CoreError::IoError(crate::error::ErrorContext::new(format!(
100                "Failed to read /proc/cpuinfo: {e}"
101            )))
102        })?;
103
104        let mut model = "Unknown CPU".to_string();
105        let mut vendor = "Unknown".to_string();
106        let mut logical_cores = 0;
107        let mut cache_l1_kb = 32;
108        let mut cache_l2_kb = 256;
109        let mut cache_l3_kb = 1024;
110        let mut flags = Vec::new();
111
112        for line in cpuinfo.lines() {
113            if line.starts_with("model name") {
114                if let Some(value) = line.split(':').nth(1) {
115                    model = value.trim().to_string();
116                }
117            } else if line.starts_with("vendor_id") {
118                if let Some(value) = line.split(':').nth(1) {
119                    vendor = value.trim().to_string();
120                }
121            } else if line.starts_with("processor") {
122                logical_cores += 1;
123            } else if line.starts_with("flags") {
124                if let Some(value) = line.split(':').nth(1) {
125                    flags = value.split_whitespace().map(|s| s.to_string()).collect();
126                }
127            }
128        }
129
130        // Try to get cache information from sysfs
131        if let Ok(cache_info) = Self::read_cache_info_linux() {
132            cache_l1_kb = cache_info.0;
133            cache_l2_kb = cache_info.1;
134            cache_l3_kb = cache_info.2;
135        }
136
137        // Get physical core count
138        let physical_cores = Self::get_physical_cores_linux().unwrap_or(logical_cores);
139
140        // Get frequency information
141        let (base_freq, max_freq) = Self::get_frequency_info_linux().unwrap_or((2.0, 2.0));
142
143        // Detect SIMD capabilities
144        let simd_capabilities = SimdCapabilities::from_flags(&flags);
145
146        // Detect architecture
147        let architecture = CpuArchitecture::detect();
148
149        Ok(Self {
150            model,
151            vendor,
152            physical_cores,
153            logical_cores,
154            base_frequency_ghz: base_freq,
155            max_frequency_ghz: max_freq,
156            cache_l1_kb,
157            cache_l2_kb,
158            cache_l3_kb,
159            simd_capabilities,
160            architecture,
161            features: flags,
162        })
163    }
164
165    /// Get cache information on Linux
166    #[cfg(target_os = "linux")]
167    fn read_cache_info_linux() -> CoreResult<(usize, usize, usize)> {
168        let mut l1_kb = 32;
169        let mut l2_kb = 256;
170        let mut l3_kb = 1024;
171
172        // Try to read cache sizes from sysfs
173        if let Ok(content) = fs::read_to_string("/sys/devices/system/cpu/cpu0/cache/index0/size") {
174            if let Ok(size) = Self::parse_cache_size(content.trim()) {
175                l1_kb = size;
176            }
177        }
178
179        if let Ok(content) = fs::read_to_string("/sys/devices/system/cpu/cpu0/cache/index2/size") {
180            if let Ok(size) = Self::parse_cache_size(content.trim()) {
181                l2_kb = size;
182            }
183        }
184
185        if let Ok(content) = fs::read_to_string("/sys/devices/system/cpu/cpu0/cache/index3/size") {
186            if let Ok(size) = Self::parse_cache_size(content.trim()) {
187                l3_kb = size;
188            }
189        }
190
191        Ok((l1_kb, l2_kb, l3_kb))
192    }
193
194    /// Parse cache size string (e.g., "32K", "256K", "8192K")
195    #[allow(dead_code)]
196    fn parse_cache_size(sizestr: &str) -> CoreResult<usize> {
197        if sizestr.ends_with('K') || sizestr.ends_with('k') {
198            let num_str = &sizestr[..sizestr.len() - 1];
199            let size = num_str.parse::<usize>().map_err(|e| {
200                CoreError::ValidationError(crate::error::ErrorContext::new(format!(
201                    "Failed to parse cache size: {e}"
202                )))
203            })?;
204            Ok(size)
205        } else if sizestr.ends_with('M') || sizestr.ends_with('m') {
206            let num_str = &sizestr[..sizestr.len() - 1];
207            let size = num_str.parse::<usize>().map_err(|e| {
208                CoreError::ValidationError(crate::error::ErrorContext::new(format!(
209                    "Failed to parse cache size: {e}"
210                )))
211            })? * 1024;
212            Ok(size)
213        } else {
214            let size = sizestr.parse::<usize>().map_err(|e| {
215                CoreError::ValidationError(crate::error::ErrorContext::new(format!(
216                    "Failed to parse cache size: {e}"
217                )))
218            })?;
219            Ok(size)
220        }
221    }
222
223    /// Get physical core count on Linux
224    #[cfg(target_os = "linux")]
225    fn get_physical_cores_linux() -> CoreResult<usize> {
226        if let Ok(content) = fs::read_to_string("/proc/cpuinfo") {
227            let mut core_ids = std::collections::HashSet::new();
228            for line in content.lines() {
229                if line.starts_with("core id") {
230                    if let Some(value) = line.split(':').nth(1) {
231                        if let Ok(core_id) = value.trim().parse::<usize>() {
232                            core_ids.insert(core_id);
233                        }
234                    }
235                }
236            }
237            if !core_ids.is_empty() {
238                return Ok(core_ids.len());
239            }
240        }
241
242        // Fallback: use available_parallelism
243        Ok(std::thread::available_parallelism()
244            .map(|n| n.get())
245            .unwrap_or(1))
246    }
247
248    /// Get frequency information on Linux
249    #[cfg(target_os = "linux")]
250    fn get_frequency_info_linux() -> CoreResult<(f64, f64)> {
251        let mut base_freq = 2.0;
252        let mut max_freq = 2.0;
253
254        // Try to read from cpufreq
255        if let Ok(content) =
256            fs::read_to_string("/sys/devices/system/cpu/cpu0/cpufreq/base_frequency")
257        {
258            if let Ok(freq_khz) = content.trim().parse::<f64>() {
259                base_freq = freq_khz / 1_000_000.0; // Convert kHz to GHz
260            }
261        }
262
263        if let Ok(content) =
264            fs::read_to_string("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq")
265        {
266            if let Ok(freq_khz) = content.trim().parse::<f64>() {
267                max_freq = freq_khz / 1_000_000.0; // Convert kHz to GHz
268            }
269        }
270
271        Ok((base_freq, max_freq))
272    }
273
274    /// Detect CPU information on Windows
275    #[cfg(target_os = "windows")]
276    fn detect_windows() -> CoreResult<Self> {
277        // For Windows, we'd use WMI or registry queries
278        // This is a simplified implementation
279        let logical_cores = std::thread::available_parallelism()
280            .map(|n| n.get())
281            .unwrap_or(1);
282
283        let physical_cores = logical_cores / 2; // Rough estimate
284
285        Ok(Self {
286            model: "Windows CPU".to_string(),
287            vendor: "Unknown".to_string(),
288            physical_cores,
289            logical_cores,
290            base_frequency_ghz: 2.5,
291            max_frequency_ghz: 3.0,
292            cache_l1_kb: 32,
293            cache_l2_kb: 256,
294            cache_l3_kb: 1024,
295            simd_capabilities: SimdCapabilities::detect(),
296            architecture: CpuArchitecture::detect(),
297            features: Vec::new(),
298        })
299    }
300
301    /// Detect CPU information on macOS
302    #[cfg(target_os = "macos")]
303    fn detect_macos() -> CoreResult<Self> {
304        // For macOS, we'd use sysctl
305        // This is a simplified implementation
306        let logical_cores = std::thread::available_parallelism()
307            .map(|n| n.get())
308            .unwrap_or(1);
309
310        let physical_cores = logical_cores; // Apple Silicon doesn't have hyperthreading
311
312        Ok(Self {
313            model: "macOS CPU".to_string(),
314            vendor: "Apple".to_string(),
315            physical_cores,
316            logical_cores,
317            base_frequency_ghz: 2.5,
318            max_frequency_ghz: 3.0,
319            cache_l1_kb: 128,
320            cache_l2_kb: 4096,
321            cache_l3_kb: 0, // Apple Silicon uses different cache hierarchy
322            simd_capabilities: SimdCapabilities::detect(),
323            architecture: CpuArchitecture::detect(),
324            features: Vec::new(),
325        })
326    }
327
328    /// Calculate performance score (0.0 to 1.0)
329    pub fn performance_score(&self) -> f64 {
330        let core_score = (self.physical_cores as f64 / 16.0).min(1.0); // Normalize to 16 cores
331        let freq_score = (self.max_frequency_ghz / 4.0).min(1.0); // Normalize to 4 GHz
332        let cache_score = (self.cache_l3_kb as f64 / 32768.0).min(1.0); // Normalize to 32MB
333        let simd_score = if self.simd_capabilities.avx512 {
334            1.0
335        } else if self.simd_capabilities.avx2 {
336            0.8
337        } else if self.simd_capabilities.sse4_2 {
338            0.6
339        } else {
340            0.3
341        };
342
343        (core_score + freq_score + cache_score + simd_score) / 4.0
344    }
345
346    /// Get optimal thread count for parallel operations
347    pub fn optimal_thread_count(&self) -> usize {
348        // Use physical cores for CPU-intensive tasks
349        // Add some extra threads for I/O bound tasks
350        let base_threads = self.physical_cores;
351        let io_threads = (self.logical_cores - self.physical_cores).min(2);
352        base_threads + io_threads
353    }
354
355    /// Get optimal chunk size based on cache hierarchy
356    pub fn optimal_chunk_size(&self) -> usize {
357        // Use L2 cache size as base, leave some room for other data
358        let l2_bytes = self.cache_l2_kb * 1024;
359        (l2_bytes * 3 / 4).max(4096) // At least 4KB
360    }
361
362    /// Check if CPU supports specific instruction set
363    pub fn supports_instruction_set(&self, instructionset: &str) -> bool {
364        self.features
365            .iter()
366            .any(|f| f.eq_ignore_ascii_case(instructionset))
367    }
368}
369
370/// SIMD instruction set capabilities
371#[derive(Debug, Clone, Default)]
372pub struct SimdCapabilities {
373    /// SSE 4.2 support
374    pub sse4_2: bool,
375    /// AVX2 support
376    pub avx2: bool,
377    /// AVX-512 support
378    pub avx512: bool,
379    /// ARM NEON support
380    pub neon: bool,
381}
382
383impl SimdCapabilities {
384    /// Detect SIMD capabilities from CPU flags
385    pub fn from_flags(flags: &[String]) -> Self {
386        let mut capabilities = Self::default();
387
388        for flag in flags {
389            match flag.as_str() {
390                "sse4_2" => capabilities.sse4_2 = true,
391                "avx2" => capabilities.avx2 = true,
392                "avx512f" | "avx512" => capabilities.avx512 = true,
393                "neon" => capabilities.neon = true,
394                _ => {}
395            }
396        }
397
398        capabilities
399    }
400
401    /// Detect SIMD capabilities using runtime detection
402    pub fn detect() -> Self {
403        let mut capabilities = Self::default();
404
405        #[cfg(target_arch = "x86_64")]
406        {
407            if is_x86_feature_detected!("sse4.2") {
408                capabilities.sse4_2 = true;
409            }
410            if is_x86_feature_detected!("avx2") {
411                capabilities.avx2 = true;
412            }
413            if is_x86_feature_detected!("avx512f") {
414                capabilities.avx512 = true;
415            }
416        }
417
418        #[cfg(target_arch = "aarch64")]
419        {
420            // NEON is standard on AArch64
421            capabilities.neon = true;
422        }
423
424        capabilities
425    }
426
427    /// Get the best available SIMD instruction set
428    pub fn best_available(&self) -> SimdInstructionSet {
429        if self.avx512 {
430            SimdInstructionSet::Avx512
431        } else if self.avx2 {
432            SimdInstructionSet::Avx2
433        } else if self.sse4_2 {
434            SimdInstructionSet::Sse42
435        } else if self.neon {
436            SimdInstructionSet::Neon
437        } else {
438            SimdInstructionSet::None
439        }
440    }
441}
442
443/// SIMD instruction set types
444#[derive(Debug, Clone, Copy, PartialEq, Eq)]
445pub enum SimdInstructionSet {
446    /// No SIMD support
447    None,
448    /// SSE 4.2
449    Sse42,
450    /// AVX2
451    Avx2,
452    /// AVX-512
453    Avx512,
454    /// ARM NEON
455    Neon,
456}
457
458impl SimdInstructionSet {
459    /// Get vector width in bytes
460    pub fn vector_width_bytes(&self) -> usize {
461        match self {
462            SimdInstructionSet::None => 8,
463            SimdInstructionSet::Sse42 => 16,
464            SimdInstructionSet::Avx2 => 32,
465            SimdInstructionSet::Avx512 => 64,
466            SimdInstructionSet::Neon => 16,
467        }
468    }
469
470    /// Get vector width in f32 elements
471    pub fn vector_width_f32(&self) -> usize {
472        self.vector_width_bytes() / 4
473    }
474
475    /// Get vector width in f64 elements
476    pub fn vector_width_f64(&self) -> usize {
477        self.vector_width_bytes() / 8
478    }
479}
480
481/// CPU architecture types
482#[derive(Debug, Clone, Copy, PartialEq, Eq)]
483pub enum CpuArchitecture {
484    /// x86-64 (AMD64)
485    X86_64,
486    /// AArch64 (ARM64)
487    AArch64,
488    /// 32-bit x86
489    X86,
490    /// 32-bit ARM
491    Arm,
492    /// Unknown architecture
493    Unknown,
494}
495
496impl CpuArchitecture {
497    /// Detect current CPU architecture
498    pub fn detect() -> Self {
499        #[cfg(target_arch = "x86_64")]
500        return CpuArchitecture::X86_64;
501
502        #[cfg(target_arch = "aarch64")]
503        return CpuArchitecture::AArch64;
504
505        #[cfg(target_arch = "x86")]
506        return CpuArchitecture::X86;
507
508        #[cfg(target_arch = "arm")]
509        return CpuArchitecture::Arm;
510
511        #[cfg(not(any(
512            target_arch = "x86_64",
513            target_arch = "aarch64",
514            target_arch = "x86",
515            target_arch = "arm"
516        )))]
517        return CpuArchitecture::Unknown;
518    }
519
520    /// Check if architecture supports specific features
521    pub fn supports_64bit(&self) -> bool {
522        matches!(self, CpuArchitecture::X86_64 | CpuArchitecture::AArch64)
523    }
524
525    /// Get native pointer size in bytes
526    pub fn pointer_size(&self) -> usize {
527        match self {
528            CpuArchitecture::X86_64 | CpuArchitecture::AArch64 => 8,
529            CpuArchitecture::X86 | CpuArchitecture::Arm => 4,
530            CpuArchitecture::Unknown => std::mem::size_of::<usize>(),
531        }
532    }
533}
534
535#[cfg(test)]
536mod tests {
537    use super::*;
538
539    #[test]
540    fn test_cpu_detection() {
541        let cpuinfo = CpuInfo::detect();
542        assert!(cpuinfo.is_ok());
543
544        let cpu = cpuinfo.unwrap();
545        assert!(cpu.logical_cores > 0);
546        assert!(cpu.physical_cores > 0);
547        assert!(cpu.physical_cores <= cpu.logical_cores);
548    }
549
550    #[test]
551    fn test_simd_detection() {
552        let simd = SimdCapabilities::detect();
553        let best = simd.best_available();
554
555        // Should at least detect some capability
556        assert_ne!(best, SimdInstructionSet::None);
557    }
558
559    #[test]
560    fn test_architecture_detection() {
561        let arch = CpuArchitecture::detect();
562        assert_ne!(arch, CpuArchitecture::Unknown);
563
564        // Test pointer size
565        assert!(arch.pointer_size() > 0);
566        assert_eq!(arch.pointer_size(), std::mem::size_of::<usize>());
567    }
568
569    #[test]
570    fn test_performance_score() {
571        let cpu = CpuInfo::default();
572        let score = cpu.performance_score();
573        assert!((0.0..=1.0).contains(&score));
574    }
575
576    #[test]
577    fn test_optimal_parameters() {
578        let cpu = CpuInfo::default();
579
580        let thread_count = cpu.optimal_thread_count();
581        assert!(thread_count > 0);
582
583        let chunk_size = cpu.optimal_chunk_size();
584        assert!(chunk_size >= 4096);
585    }
586
587    #[test]
588    fn test_vector_widths() {
589        let avx2 = SimdInstructionSet::Avx2;
590        assert_eq!(avx2.vector_width_bytes(), 32);
591        assert_eq!(avx2.vector_width_f32(), 8);
592        assert_eq!(avx2.vector_width_f64(), 4);
593    }
594
595    #[test]
596    fn test_cache_size_parsing() {
597        assert_eq!(CpuInfo::parse_cache_size("32K").unwrap(), 32);
598        assert_eq!(CpuInfo::parse_cache_size("256k").unwrap(), 256);
599        assert_eq!(CpuInfo::parse_cache_size("8M").unwrap(), 8192);
600        assert_eq!(CpuInfo::parse_cache_size("1024").unwrap(), 1024);
601    }
602}