use crate::{
AffinityMask, CacheInfo, CoreKind, CpuFeatures, L2Domain, L3Domain, Lp, Result, Vendor,
};
#[must_use]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CpuInfo {
pub lps: Vec<Lp>,
pub core_count: u16,
pub socket_count: u8,
pub numa_node_count: u8,
pub kind_core_counts: [u16; CoreKind::COUNT],
pub l3_domains: Vec<L3Domain>,
pub l2_domains: Vec<L2Domain>,
pub l1d: [CacheInfo; CoreKind::COUNT],
pub l1i: [CacheInfo; CoreKind::COUNT],
pub l2: [CacheInfo; CoreKind::COUNT],
pub vendor: Vendor,
pub model_name: String,
pub features: CpuFeatures,
}
impl CpuInfo {
#[must_use = "detecting topology has a cost; keep and reuse the returned CpuInfo"]
pub fn detect() -> Result<Self> {
#[cfg(target_os = "linux")]
{
crate::platform::linux::cpu::detect_cpu_info()
}
#[cfg(target_os = "macos")]
{
crate::platform::macos::cpu::detect_cpu_info()
}
#[cfg(target_os = "windows")]
{
crate::platform::windows::cpu::detect_cpu_info()
}
#[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
{
Err(crate::Error::Unsupported(
"CPU information detection is not supported on this platform.".to_string(),
))
}
}
pub fn num_physical_cores(&self) -> usize {
self.core_count as usize
}
pub fn num_logical_cores(&self) -> usize {
self.lps.len()
}
pub fn num_performance_cores(&self) -> usize {
self.kind_core_counts[CoreKind::Performance.index()] as usize
}
pub fn num_efficiency_cores(&self) -> usize {
self.kind_core_counts[CoreKind::Efficiency.index()] as usize
}
pub fn num_lp_efficiency_cores(&self) -> usize {
self.kind_core_counts[CoreKind::LpEfficiency.index()] as usize
}
pub fn is_hybrid(&self) -> bool {
let kinds_present = [
CoreKind::Performance,
CoreKind::Efficiency,
CoreKind::LpEfficiency,
]
.iter()
.filter(|k| self.kind_core_counts[k.index()] > 0)
.count();
kinds_present > 1
}
pub fn logical_processor_ids(&self) -> Vec<usize> {
self.lps.iter().map(|lp| lp.os_id as usize).collect()
}
pub fn all_cores_mask(&self) -> AffinityMask {
self.mask_where(|_| true)
}
pub fn kind_mask(&self, kind: CoreKind) -> AffinityMask {
self.mask_where(|lp| lp.kind == kind)
}
pub fn performance_core_mask(&self) -> AffinityMask {
self.kind_mask(CoreKind::Performance)
}
pub fn efficiency_core_mask(&self) -> AffinityMask {
self.kind_mask(CoreKind::Efficiency)
}
pub fn lp_efficiency_core_mask(&self) -> AffinityMask {
self.kind_mask(CoreKind::LpEfficiency)
}
pub fn primary_thread_mask(&self) -> AffinityMask {
self.mask_where(|lp| lp.smt_index == 0)
}
pub fn l3_domain_mask(&self, domain: u8) -> AffinityMask {
self.l3_domains
.get(domain as usize)
.map(|d| d.mask)
.unwrap_or_else(AffinityMask::empty)
}
pub fn l2_domain_mask(&self, domain: u16) -> AffinityMask {
self.l2_domains
.get(domain as usize)
.map(|d| d.mask)
.unwrap_or_else(AffinityMask::empty)
}
pub fn numa_node_mask(&self, node: u8) -> AffinityMask {
self.mask_where(|lp| lp.numa_node == node)
}
fn mask_where(&self, pred: impl Fn(&Lp) -> bool) -> AffinityMask {
let mut mask = AffinityMask::empty();
for lp in self.lps.iter().filter(|lp| pred(lp)) {
mask.add(lp.os_id as usize);
}
mask
}
pub(crate) fn normalize_domain_order(&mut self) {
let l3_remap = sort_domains_by_lowest_lp(&mut self.l3_domains, |d| &d.mask);
let l2_remap = sort_domains_by_lowest_lp(&mut self.l2_domains, |d| &d.mask);
for lp in &mut self.lps {
if lp.l3_domain != Lp::NO_L3 {
lp.l3_domain = l3_remap[lp.l3_domain as usize] as u8;
}
if lp.l2_domain != Lp::NO_L2 {
lp.l2_domain = l2_remap[lp.l2_domain as usize] as u16;
}
}
}
}
fn sort_domains_by_lowest_lp<D: Clone>(
domains: &mut Vec<D>,
mask_of: impl Fn(&D) -> &AffinityMask,
) -> Vec<usize> {
let mut order: Vec<usize> = (0..domains.len()).collect();
order.sort_by_key(|&i| mask_of(&domains[i]).iter().next().unwrap_or(usize::MAX));
let mut remap = vec![0usize; domains.len()];
for (new_idx, &old_idx) in order.iter().enumerate() {
remap[old_idx] = new_idx;
}
*domains = order.iter().map(|&i| domains[i].clone()).collect();
remap
}