Skip to main content

dupehell_core/
cpu_affinity.rs

1// DupeHell -- MIT License
2//
3// Synthetic multi-domain dataset generator for record linkage benchmarking.
4// EDUCATIONAL AND RESEARCH PURPOSES ONLY -- see ETHICS.md for prohibited uses.
5// No liability for misuse.
6
7//! Optional opt-in pinning of the process to "performance" cores on
8//! hybrid (P-core/E-core) CPUs.
9//!
10//! On hybrid topologies, Rayon workers that land on an E-core become
11//! stragglers relative to their P-core siblings at every parallel sync
12//! point, which can degrade throughput disproportionately at high record
13//! counts. This restricts the process (and therefore Rayon's default
14//! global pool, spawned lazily on first use) to the highest
15//! `EfficiencyClass` reported by Windows. No-op, returns `false`, on
16//! non-Windows targets or non-hybrid CPUs.
17
18#[cfg(target_os = "windows")]
19pub fn pin_to_p_cores() -> bool {
20    use windows::Win32::Foundation::HANDLE;
21    use windows::Win32::System::SystemInformation::{
22        GetLogicalProcessorInformationEx, RelationProcessorCore,
23        SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX,
24    };
25    use windows::Win32::System::Threading::{GetCurrentProcess, SetProcessAffinityMask};
26
27    // First call to learn the required buffer size.
28    let mut len: u32 = 0;
29    unsafe {
30        let _ = GetLogicalProcessorInformationEx(RelationProcessorCore, None, &mut len);
31    }
32    if len == 0 {
33        return false;
34    }
35
36    let mut buf: Vec<u8> = vec![0u8; len as usize];
37    let ok = unsafe {
38        GetLogicalProcessorInformationEx(
39            RelationProcessorCore,
40            Some(
41                buf.as_mut_ptr()
42                    .cast::<SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX>(),
43            ),
44            &mut len,
45        )
46    };
47    if ok.is_err() {
48        return false;
49    }
50
51    // Walk the variable-length list of entries, each describing one
52    // physical core (EfficiencyClass + the logical-processor mask of its
53    // hyperthread siblings, group 0 only -- fine below 64 logical CPUs).
54    let mut offset = 0usize;
55    let mut max_class: u8 = 0;
56    let mut entries: Vec<(u8, u64)> = Vec::new();
57    while offset < buf.len() {
58        let entry = unsafe {
59            &*(buf.as_ptr().add(offset) as *const SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)
60        };
61        let size = entry.Size as usize;
62        if size == 0 {
63            break;
64        }
65        let core = unsafe { &entry.Anonymous.Processor };
66        let class = core.EfficiencyClass;
67        let mask = core.GroupMask[0].Mask as u64;
68        max_class = max_class.max(class);
69        entries.push((class, mask));
70        offset += size;
71    }
72
73    // No hybrid topology (single efficiency class) -- nothing to gain.
74    if entries.iter().all(|(class, _)| *class == entries[0].0) {
75        return false;
76    }
77
78    let p_core_mask: u64 = entries
79        .iter()
80        .filter(|(class, _)| *class == max_class)
81        .fold(0u64, |acc, (_, mask)| acc | mask);
82    if p_core_mask == 0 {
83        return false;
84    }
85
86    let handle: HANDLE = unsafe { GetCurrentProcess() };
87    unsafe { SetProcessAffinityMask(handle, p_core_mask as usize) }.is_ok()
88}
89
90#[cfg(not(target_os = "windows"))]
91pub fn pin_to_p_cores() -> bool {
92    false
93}