use core_affinity::{self, CoreId};
use parking_lot::{Mutex, MutexGuard};
use std::collections::HashSet;
use std::ops::{BitOr, BitOrAssign, Sub, SubAssign};
use std::sync::LazyLock;
pub static CORES: LazyLock<CoreIds> = LazyLock::new(CoreIds::from_system);
pub struct CoreIds(Mutex<Vec<Core>>);
impl CoreIds {
fn from_system() -> Self {
Self::new(
core_affinity::get_core_ids()
.map(|core_ids| core_ids.into_iter().map(Core::from).collect())
.unwrap_or_default(),
)
}
fn new(core_ids: Vec<Core>) -> Self {
Self(Mutex::new(core_ids))
}
fn get(&self, index: usize) -> Option<Core> {
self.0.lock().get(index).cloned()
}
fn update_from(&self, mut new_ids: HashSet<CoreId>) -> usize {
let mut cur_ids = self.0.lock();
cur_ids.iter_mut().for_each(|core| {
if new_ids.contains(&core.id) {
core.available = true;
new_ids.remove(&core.id);
} else {
core.available = false;
}
});
let num_new_ids = new_ids.len();
cur_ids.extend(new_ids.into_iter().map(Core::from));
num_new_ids
}
pub fn refresh(&self) -> usize {
let new_ids: HashSet<_> = core_affinity::get_core_ids()
.map(|core_ids| core_ids.into_iter().collect())
.unwrap_or_default();
self.update_from(new_ids)
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Cores(Vec<usize>);
impl Cores {
pub fn first(n: usize) -> Self {
Self(Vec::from_iter(0..n.min(num_cpus::get())))
}
pub fn all() -> Self {
Self::from(0..num_cpus::get())
}
pub fn append(&mut self, index: usize) -> bool {
if !self.0.contains(&index) {
self.0.push(index);
true
} else {
false
}
}
pub fn union(&mut self, other: &Self) -> usize {
(*other.0)
.iter()
.filter_map(|index| self.append(*index).then_some(()))
.count()
}
pub fn get(&self, index: usize) -> Option<Core> {
self.0
.get(index)
.and_then(|&index| CORES.get(index))
.filter(|core| core.available)
}
pub fn iter(&self) -> impl Iterator<Item = (usize, Option<Core>)> {
CoreIter::new(self.0.iter().cloned())
}
}
impl BitOr for Cores {
type Output = Self;
fn bitor(mut self, rhs: Self) -> Self::Output {
self |= rhs;
self
}
}
impl BitOrAssign for Cores {
fn bitor_assign(&mut self, rhs: Self) {
let mut rhs_indexes = rhs.0;
rhs_indexes.retain(|index| !self.0.contains(index));
self.0.extend(rhs_indexes);
}
}
impl Sub for Cores {
type Output = Self;
fn sub(mut self, rhs: Self) -> Self::Output {
self -= rhs;
self
}
}
impl SubAssign for Cores {
fn sub_assign(&mut self, rhs: Self) {
self.0.retain(|i| !rhs.0.contains(i));
}
}
impl FromIterator<usize> for Cores {
fn from_iter<T: IntoIterator<Item = usize>>(iter: T) -> Self {
Self(Vec::from_iter(iter))
}
}
impl<I: IntoIterator<Item = usize>> From<I> for Cores {
fn from(value: I) -> Self {
Self(Vec::from_iter(value))
}
}
pub struct CoreIter<'a, I: Iterator<Item = usize>> {
index_iter: I,
cores: MutexGuard<'a, Vec<Core>>,
}
impl<I: Iterator<Item = usize>> CoreIter<'_, I> {
fn new(index_iter: I) -> Self {
Self {
index_iter,
cores: CORES.0.lock(),
}
}
}
impl<I: Iterator<Item = usize>> Iterator for CoreIter<'_, I> {
type Item = (usize, Option<Core>);
fn next(&mut self) -> Option<Self::Item> {
let index = self.index_iter.next()?;
let core = self.cores.get(index).cloned().filter(|core| core.available);
Some((index, core))
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Core {
id: CoreId,
available: bool,
}
impl Core {
fn new(id: CoreId, available: bool) -> Self {
Self { id, available }
}
pub fn try_pin_current(&self) -> bool {
self.available && core_affinity::set_for_current(self.id)
}
}
impl From<CoreId> for Core {
fn from(id: CoreId) -> Self {
Self::new(id, true)
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn test_core_ids() {
let core_ids = CoreIds::new((0..10usize).map(|id| Core::from(CoreId { id })).collect());
assert_eq!(
(0..10)
.flat_map(|i| core_ids.get(i).map(|id| id.id))
.collect::<Vec<_>>(),
(0..10).map(|id| CoreId { id }).collect::<Vec<_>>()
);
assert!((0..10).all(|i| core_ids.get(i).map(|id| id.available).unwrap_or_default()));
let new_ids: HashSet<CoreId> = vec![10, 11, 1, 3, 5, 7, 9]
.into_iter()
.map(|id| CoreId { id })
.collect();
let num_added = core_ids.update_from(new_ids);
assert_eq!(num_added, 2);
let mut new_core_ids = (0..12)
.flat_map(|i| core_ids.get(i).map(|id| id.id))
.collect::<Vec<_>>();
new_core_ids.sort();
assert_eq!(
new_core_ids,
(0..12).map(|id| CoreId { id }).collect::<Vec<_>>()
);
assert_eq!(
(0..12)
.flat_map(|i| core_ids.get(i))
.filter(|id| id.available)
.count(),
7
);
}
#[test]
fn test_empty() {
assert_eq!(Cores::default().0.len(), 0);
}
#[test]
fn test_first() {
let max = num_cpus::get();
for n in 1..=max {
assert_eq!(Cores::first(n).0.len(), n);
}
assert_eq!(Cores::first(max + 1).0.len(), max);
}
#[test]
fn test_all() {
let max = num_cpus::get();
assert_eq!(Cores::all().0.len(), max);
}
#[test]
fn test_append() {
let mut a = Cores::from(0..4);
a.append(4);
assert_eq!(a, Cores::from(0..5));
}
#[test]
fn test_union() {
let mut a = Cores::from(0..4);
let b = Cores::from(3..6);
a.union(&b);
assert_eq!(a, Cores::from(0..6));
}
#[test]
fn test_ops() {
let a = Cores::from(0..4);
let b = Cores::from(3..6);
assert_eq!(a.clone() | b.clone(), Cores::from(0..6));
assert_eq!(a.clone() - b.clone(), Cores::from(0..3));
assert_eq!(b.clone() - a.clone(), Cores::from(4..6));
}
#[test]
fn test_assign_ops() {
let mut a = Cores::from(0..4);
let b = Cores::from(3..6);
a |= b;
assert_eq!(a, Cores::from(0..6));
let c = Cores::from(0..3);
a -= c;
assert_eq!(a, Cores::from(3..6))
}
#[test]
fn test_iter() {
let core_ids = core_affinity::get_core_ids().unwrap();
let n = core_ids.len();
let pairs: Vec<_> = Cores::from(0..n).iter().collect();
assert_eq!(pairs.len(), n);
}
}