photon_ring/affinity.rs
1// Copyright 2026 Photon Ring Contributors
2// SPDX-License-Identifier: Apache-2.0
3
4//! CPU core affinity helpers for deterministic cross-core latency.
5//!
6//! Pinning publisher and subscriber threads to specific CPU cores
7//! eliminates OS scheduler jitter and ensures consistent cache-coherence
8//! transfer times.
9//!
10//! ## NUMA considerations
11//!
12//! On multi-socket systems, pin publisher and subscriber threads to
13//! cores on the **same** socket. Cross-socket communication (QPI/UPI)
14//! adds ~100-200 ns of additional latency per cache-line transfer
15//! compared to intra-socket L3 snoops (~40-55 ns on Intel Comet Lake).
16//!
17//! ## Example
18//!
19//! ```no_run
20//! use photon_ring::affinity;
21//!
22//! let cores = affinity::available_cores();
23//! assert!(cores.len() >= 2, "need at least 2 cores");
24//!
25//! // Pin to the first core
26//! assert!(affinity::pin_to_core(cores[0]));
27//! ```
28
29use alloc::vec::Vec;
30
31pub use core_affinity2::CoreId;
32
33/// Pin the current thread to a specific CPU core.
34///
35/// Returns `true` on success, `false` if the OS rejected the request
36/// (e.g., invalid core ID, insufficient permissions, or unsupported
37/// platform).
38///
39/// # Example
40///
41/// ```no_run
42/// use photon_ring::affinity;
43///
44/// let cores = affinity::available_cores();
45/// assert!(affinity::pin_to_core(cores[0]), "failed to pin");
46/// ```
47#[inline]
48pub fn pin_to_core(core_id: CoreId) -> bool {
49 core_id.set_affinity().is_ok()
50}
51
52/// Return the list of CPU cores available to this process.
53///
54/// The returned [`CoreId`]s can be passed directly to [`pin_to_core`].
55/// The list order matches the OS core numbering (logical CPUs including
56/// SMT siblings).
57pub fn available_cores() -> Vec<CoreId> {
58 core_affinity2::get_core_ids().unwrap_or_default()
59}
60
61/// Pin the current thread to a core by its numeric index.
62///
63/// Convenience wrapper around [`pin_to_core`] that looks up the core by
64/// index in [`available_cores`]. Returns `true` on success, `false` if
65/// the index is out of range or the OS rejects the request.
66///
67/// # Example
68///
69/// ```no_run
70/// use photon_ring::affinity;
71///
72/// assert!(affinity::pin_to_core_id(0), "failed to pin to core 0");
73/// ```
74pub fn pin_to_core_id(index: usize) -> bool {
75 let cores = available_cores();
76 match cores.get(index) {
77 Some(&core_id) => pin_to_core(core_id),
78 None => false,
79 }
80}
81
82/// Return the number of CPU cores available to this process.
83pub fn core_count() -> usize {
84 available_cores().len()
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn available_cores_is_nonempty() {
93 let cores = available_cores();
94 assert!(!cores.is_empty(), "expected at least one core");
95 }
96
97 #[test]
98 fn core_count_matches_available() {
99 assert_eq!(core_count(), available_cores().len());
100 }
101
102 #[test]
103 fn pin_to_first_core() {
104 let cores = available_cores();
105 assert!(pin_to_core(cores[0]), "failed to pin to first core");
106 }
107
108 #[test]
109 fn pin_to_core_id_valid() {
110 assert!(pin_to_core_id(0), "failed to pin to core index 0");
111 }
112
113 #[test]
114 fn pin_to_core_id_out_of_range() {
115 assert!(!pin_to_core_id(usize::MAX), "should fail for invalid index");
116 }
117}