use std::collections::VecDeque;
use std::fmt::Debug;
use std::num::NonZero;
use foldhash::{HashMap, HashMapExt, HashSet, HashSetExt};
use itertools::Itertools;
use nonempty::NonEmpty;
use rand::prelude::*;
use rand::rng;
use crate::pal::Platform;
use crate::{
EfficiencyClass, MemoryRegionId, Processor, ProcessorId, ProcessorSet, SystemHardware,
};
#[derive(Clone, Debug)]
pub struct ProcessorSetBuilder {
processor_type_selector: ProcessorTypeSelector,
memory_region_selector: MemoryRegionSelector,
except_indexes: HashSet<ProcessorId>,
obey_resource_quota: bool,
source_processor_ids: Option<HashSet<ProcessorId>>,
hardware: SystemHardware,
}
impl ProcessorSetBuilder {
#[must_use]
pub(crate) fn with_internals(hardware: SystemHardware) -> Self {
Self {
processor_type_selector: ProcessorTypeSelector::Any,
memory_region_selector: MemoryRegionSelector::Any,
except_indexes: HashSet::new(),
obey_resource_quota: false,
source_processor_ids: None,
hardware,
}
}
#[must_use]
pub(crate) fn source_processors(mut self, processors: &NonEmpty<Processor>) -> Self {
let ids: HashSet<ProcessorId> = processors.iter().map(Processor::id).collect();
self.source_processor_ids = Some(ids);
self
}
#[must_use]
pub fn performance_processors_only(mut self) -> Self {
self.processor_type_selector = ProcessorTypeSelector::Performance;
self
}
#[must_use]
pub fn efficiency_processors_only(mut self) -> Self {
self.processor_type_selector = ProcessorTypeSelector::Efficiency;
self
}
#[must_use]
pub fn different_memory_regions(mut self) -> Self {
self.memory_region_selector = MemoryRegionSelector::RequireDifferent;
self
}
#[must_use]
pub fn same_memory_region(mut self) -> Self {
self.memory_region_selector = MemoryRegionSelector::RequireSame;
self
}
#[must_use]
pub fn prefer_different_memory_regions(mut self) -> Self {
self.memory_region_selector = MemoryRegionSelector::PreferDifferent;
self
}
#[must_use]
pub fn prefer_same_memory_region(mut self) -> Self {
self.memory_region_selector = MemoryRegionSelector::PreferSame;
self
}
#[must_use]
pub fn filter(mut self, predicate: impl Fn(&Processor) -> bool) -> Self {
for processor in self.candidate_processors() {
if !predicate(&processor) {
self.except_indexes.insert(processor.id());
}
}
self
}
#[must_use]
pub fn except<'a, I>(mut self, processors: I) -> Self
where
I: IntoIterator<Item = &'a Processor>,
{
for processor in processors {
self.except_indexes.insert(processor.id());
}
self
}
#[must_use]
pub fn where_available_for_current_thread(mut self) -> Self {
let current_thread_processors = self.hardware.platform().current_thread_processors();
for processor in self.candidate_processors() {
if !current_thread_processors.contains(&processor.id()) {
self.except_indexes.insert(processor.id());
}
}
self
}
#[must_use]
pub fn enforce_resource_quota(mut self) -> Self {
self.obey_resource_quota = true;
self
}
#[must_use]
pub fn take(self, count: NonZero<usize>) -> Option<ProcessorSet> {
if let Some(max_count) = self.resource_quota_processor_count_limit()
&& count.get() > max_count
{
return None;
}
let candidates = self.candidates_by_memory_region();
if candidates.is_empty() {
return None;
}
let processors = match self.memory_region_selector {
MemoryRegionSelector::Any => {
let all_processors = candidates
.values()
.flat_map(|x| x.iter().cloned())
.collect::<Vec<_>>();
if all_processors.len() < count.get() {
return None;
}
all_processors
.choose_multiple(&mut rng(), count.get())
.cloned()
.collect_vec()
}
MemoryRegionSelector::PreferSame => {
let count = count.get();
let mut remaining_memory_regions = candidates.keys().copied().collect_vec();
remaining_memory_regions.shuffle(&mut rng());
remaining_memory_regions.sort_unstable_by_key(|x| {
candidates
.get(x)
.expect("region must exist - we just got it from there")
.len()
.min(count)
});
remaining_memory_regions.reverse();
let mut remaining_memory_regions = VecDeque::from(remaining_memory_regions);
let mut processors: Vec<Processor> = Vec::with_capacity(count);
while processors.len() < count {
let memory_region = remaining_memory_regions.pop_front()?;
let processors_in_region = candidates.get(&memory_region).expect(
"we picked an existing key from an existing HashSet - the values must exist",
);
let choose_count = count.min(processors_in_region.len());
let region_processors = processors_in_region
.choose_multiple(&mut rng(), choose_count)
.cloned();
processors.extend(region_processors);
}
processors
}
MemoryRegionSelector::RequireSame => {
let qualifying_memory_regions = candidates
.iter()
.filter_map(|(region, processors)| {
if processors.len() < count.get() {
return None;
}
Some(region)
})
.collect_vec();
let memory_region = qualifying_memory_regions.choose(&mut rng())?;
let processors = candidates.get(memory_region).expect(
"we picked an existing key for an existing HashSet - the values must exist",
);
processors
.choose_multiple(&mut rng(), count.get())
.cloned()
.collect_vec()
}
MemoryRegionSelector::PreferDifferent => {
let mut candidates = candidates;
let mut processors = Vec::with_capacity(count.get());
while processors.len() < count.get() {
if candidates.is_empty() {
return None;
}
for remaining_processors in candidates.values_mut() {
let (index, processor) =
remaining_processors.iter().enumerate().choose(&mut rng())?;
let processor = processor.clone();
remaining_processors.remove(index);
processors.push(processor);
if processors.len() == count.get() {
break;
}
}
candidates.retain(|_, remaining_processors| !remaining_processors.is_empty());
}
processors
}
MemoryRegionSelector::RequireDifferent => {
if candidates.len() < count.get() {
return None;
}
candidates
.iter()
.choose_multiple(&mut rng(), count.get())
.into_iter()
.map(|(_, processors)| {
processors.iter().choose(&mut rng()).cloned().expect(
"we are picking one item from a non-empty list - item must exist",
)
})
.collect_vec()
}
};
Some(ProcessorSet::new(
NonEmpty::from_vec(processors)?,
self.hardware,
))
}
#[must_use]
#[cfg_attr(test, mutants::skip)] pub fn take_all(self) -> Option<ProcessorSet> {
let candidates = self.candidates_by_memory_region();
if candidates.is_empty() {
return None;
}
let processors = match self.memory_region_selector {
MemoryRegionSelector::Any
| MemoryRegionSelector::PreferSame
| MemoryRegionSelector::PreferDifferent => {
candidates
.values()
.flat_map(|x| x.iter().cloned())
.collect()
}
MemoryRegionSelector::RequireSame => {
let memory_region = candidates
.keys()
.choose(&mut rng())
.expect("we picked a random existing index - element must exist");
let processors = candidates.get(memory_region).expect(
"we picked an existing key for an existing HashSet - the values must exist",
);
processors.clone()
}
MemoryRegionSelector::RequireDifferent => {
let processors = candidates.values().map(|processors| {
processors
.choose(&mut rng())
.cloned()
.expect("we picked a random item from a non-empty list - item must exist")
});
processors.collect()
}
};
let processors = self.reduce_processors_until_under_quota(processors);
Some(ProcessorSet::new(
NonEmpty::from_vec(processors)?,
self.hardware,
))
}
fn reduce_processors_until_under_quota(&self, processors: Vec<Processor>) -> Vec<Processor> {
let Some(max_count) = self.resource_quota_processor_count_limit() else {
return processors;
};
let mut processors = processors;
while processors.len() > max_count {
processors
.pop()
.expect("guarded by len-check in loop condition");
}
processors
}
#[must_use]
pub fn take_exact(self, processors: NonEmpty<Processor>) -> ProcessorSet {
let candidates = self.candidates_by_memory_region();
let candidate_ids: HashSet<_> = candidates
.values()
.flat_map(|v| v.iter().map(Processor::id))
.collect();
for processor in &processors {
assert!(
candidate_ids.contains(&processor.id()),
"processor {} is not in the candidate set",
processor.id()
);
}
ProcessorSet::new(processors, self.hardware)
}
fn candidates_by_memory_region(&self) -> HashMap<MemoryRegionId, Vec<Processor>> {
let candidates_iter = self
.candidate_processors()
.into_iter()
.filter_map(move |p| {
if self.except_indexes.contains(&p.id()) {
return None;
}
let is_acceptable_type = match self.processor_type_selector {
ProcessorTypeSelector::Any => true,
ProcessorTypeSelector::Performance => {
p.efficiency_class() == EfficiencyClass::Performance
}
ProcessorTypeSelector::Efficiency => {
p.efficiency_class() == EfficiencyClass::Efficiency
}
};
if !is_acceptable_type {
return None;
}
Some((p.memory_region_id(), p))
});
let mut candidates = HashMap::new();
for (region, processor) in candidates_iter {
candidates
.entry(region)
.or_insert_with(Vec::new)
.push(processor);
}
candidates
}
fn candidate_processors(&self) -> Vec<Processor> {
let all = self.all_processors();
match &self.source_processor_ids {
Some(source_ids) => all
.into_iter()
.filter(|p| source_ids.contains(&p.id()))
.collect(),
None => all.into_iter().collect(),
}
}
fn all_processors(&self) -> NonEmpty<Processor> {
self.hardware
.platform()
.get_all_processors()
.map(Processor::new)
}
fn resource_quota_processor_count_limit(&self) -> Option<usize> {
if self.obey_resource_quota {
let max_processor_time = self.hardware.platform().max_processor_time();
#[expect(clippy::cast_sign_loss, reason = "quota cannot be negative")]
#[expect(
clippy::cast_possible_truncation,
reason = "we are correctly rounding to avoid the problem"
)]
let max_processor_count = max_processor_time.floor() as usize;
Some(max_processor_count.max(1))
} else {
None
}
}
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
enum MemoryRegionSelector {
#[default]
Any,
RequireSame,
RequireDifferent,
PreferSame,
PreferDifferent,
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
enum ProcessorTypeSelector {
#[default]
Any,
Performance,
Efficiency,
}
#[cfg(not(miri))] #[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests_real {
use new_zealand::nz;
use crate::SystemHardware;
#[test]
fn spawn_on_any_processor() {
let set = SystemHardware::current().processors();
let result = set.spawn_thread(move |_| 1234).join().unwrap();
assert_eq!(result, 1234);
}
#[test]
fn spawn_on_every_processor() {
let set = SystemHardware::current().processors();
let processor_count = set.len();
let join_handles = set.spawn_threads(move |_| 4567);
assert_eq!(join_handles.len(), processor_count);
for handle in join_handles {
let result = handle.join().unwrap();
assert_eq!(result, 4567);
}
}
#[test]
fn filter_by_memory_region() {
let hw = SystemHardware::current();
hw.processors()
.to_builder()
.same_memory_region()
.take_all()
.unwrap();
hw.processors()
.to_builder()
.same_memory_region()
.take(nz!(1))
.unwrap();
hw.processors()
.to_builder()
.different_memory_regions()
.take_all()
.unwrap();
hw.processors()
.to_builder()
.different_memory_regions()
.take(nz!(1))
.unwrap();
}
#[test]
fn filter_by_efficiency_class() {
let hw = SystemHardware::current();
hw.processors()
.to_builder()
.performance_processors_only()
.take_all()
.unwrap();
hw.processors()
.to_builder()
.performance_processors_only()
.take(nz!(1))
.unwrap();
drop(
hw.processors()
.to_builder()
.efficiency_processors_only()
.take_all(),
);
drop(
hw.processors()
.to_builder()
.efficiency_processors_only()
.take(nz!(1)),
);
}
#[test]
fn filter_in_all() {
let hw = SystemHardware::current();
let starting_set = hw.processors();
let processors = starting_set
.to_builder()
.filter(|_| true)
.take_all()
.unwrap();
let processor_count = starting_set.len();
assert_eq!(processors.len(), processor_count);
}
#[test]
fn filter_out_all() {
let hw = SystemHardware::current();
assert!(
hw.processors()
.to_builder()
.filter(|_| false)
.take_all()
.is_none()
);
}
#[test]
fn except_all() {
let hw = SystemHardware::current();
let starting_set = hw.processors();
assert!(
starting_set
.to_builder()
.except(starting_set.processors().iter())
.take_all()
.is_none()
);
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use std::panic::{RefUnwindSafe, UnwindSafe};
use new_zealand::nz;
use nonempty::nonempty;
use static_assertions::assert_impl_all;
use super::*;
use crate::fake::{HardwareBuilder, ProcessorBuilder};
assert_impl_all!(ProcessorSetBuilder: UnwindSafe, RefUnwindSafe);
fn proc(id: u32, memory_region: u32, efficiency_class: EfficiencyClass) -> ProcessorBuilder {
ProcessorBuilder::new()
.id(id)
.memory_region(memory_region)
.efficiency_class(efficiency_class)
}
#[test]
fn smoke_test() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 0, EfficiencyClass::Performance)),
);
let set = hw.processors().to_builder().take_all().unwrap();
assert_eq!(set.len(), 2);
}
#[test]
fn efficiency_class_filter_take() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 0, EfficiencyClass::Performance)),
);
let set = hw
.processors()
.to_builder()
.efficiency_processors_only()
.take_all()
.unwrap();
assert_eq!(set.len(), 1);
assert_eq!(set.processors().first().id(), 0);
}
#[test]
fn efficiency_class_filter_take_all() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 0, EfficiencyClass::Performance)),
);
let set = hw
.processors()
.to_builder()
.efficiency_processors_only()
.take_all()
.unwrap();
assert_eq!(set.len(), 1);
assert_eq!(set.processors().first().id(), 0);
}
#[test]
fn take_n_processors() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 0, EfficiencyClass::Performance))
.processor(proc(2, 0, EfficiencyClass::Efficiency)),
);
let set = hw.processors().to_builder().take(nz!(2)).unwrap();
assert_eq!(set.len(), 2);
}
#[test]
fn take_n_not_enough_processors() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 0, EfficiencyClass::Performance))
.max_processor_time(2.0),
);
let set = hw.processors().to_builder().take(nz!(3));
assert!(set.is_none());
}
#[test]
fn take_n_not_enough_processor_time_quota() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 0, EfficiencyClass::Performance))
.max_processor_time(1.0),
);
let set = hw
.all_processors()
.to_builder()
.enforce_resource_quota()
.take(nz!(2));
assert!(set.is_none());
}
#[test]
fn take_n_quota_floors_to_limit() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 0, EfficiencyClass::Performance))
.max_processor_time(1.5),
);
let set = hw
.all_processors()
.to_builder()
.enforce_resource_quota()
.take(nz!(2));
assert!(set.is_none());
}
#[test]
fn take_n_exactly_at_quota_limit() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 0, EfficiencyClass::Performance))
.max_processor_time(2.0),
);
let set = hw
.all_processors()
.to_builder()
.enforce_resource_quota()
.take(nz!(2));
assert!(set.is_some());
assert_eq!(set.unwrap().len(), 2);
}
#[test]
fn take_n_under_quota_succeeds() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 0, EfficiencyClass::Performance))
.processor(proc(2, 0, EfficiencyClass::Efficiency))
.max_processor_time(3.0),
);
let set = hw
.all_processors()
.to_builder()
.enforce_resource_quota()
.take(nz!(2));
assert!(set.is_some());
assert_eq!(set.unwrap().len(), 2);
}
#[test]
fn take_n_quota_not_enforced_by_default() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 0, EfficiencyClass::Performance))
.max_processor_time(1.0),
);
let limited_set = hw.processors();
assert_eq!(limited_set.len(), 1);
let set = hw.all_processors().to_builder().take(nz!(2));
assert_eq!(set.unwrap().len(), 2);
}
#[test]
fn take_n_quota_limit_min_1() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 0, EfficiencyClass::Performance))
.max_processor_time(0.001),
);
let set = hw
.all_processors()
.to_builder()
.enforce_resource_quota()
.take(nz!(1));
assert_eq!(set.unwrap().len(), 1);
}
#[test]
fn take_all_rounds_down_quota() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 0, EfficiencyClass::Performance))
.max_processor_time(1.999),
);
let set = hw
.all_processors()
.to_builder()
.enforce_resource_quota()
.take_all()
.unwrap();
assert_eq!(set.len(), 1);
}
#[test]
fn take_all_min_1_despite_quota() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 0, EfficiencyClass::Performance))
.max_processor_time(0.001),
);
let set = hw
.all_processors()
.to_builder()
.enforce_resource_quota()
.take_all()
.unwrap();
assert_eq!(set.len(), 1);
}
#[test]
fn take_all_not_enough_processors() {
let hw = SystemHardware::fake(HardwareBuilder::new().processor(proc(
0,
0,
EfficiencyClass::Efficiency,
)));
let set = hw
.processors()
.to_builder()
.performance_processors_only()
.take_all();
assert!(set.is_none());
}
#[test]
fn except_filter_take() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 0, EfficiencyClass::Performance)),
);
let builder = hw.processors().to_builder();
let except_set = builder.clone().filter(|p| p.id() == 0).take_all().unwrap();
assert_eq!(except_set.len(), 1);
let set = builder.except(except_set.processors()).take_all().unwrap();
assert_eq!(set.len(), 1);
assert_eq!(set.processors().first().id(), 1);
}
#[test]
fn except_filter_take_all() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 0, EfficiencyClass::Performance)),
);
let builder = hw.processors().to_builder();
let except_set = builder.clone().filter(|p| p.id() == 0).take_all().unwrap();
assert_eq!(except_set.len(), 1);
let set = builder.except(except_set.processors()).take_all().unwrap();
assert_eq!(set.len(), 1);
assert_eq!(set.processors().first().id(), 1);
}
#[test]
fn custom_filter_take() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 0, EfficiencyClass::Performance)),
);
let set = hw
.processors()
.to_builder()
.filter(|p| p.id() == 1)
.take_all()
.unwrap();
assert_eq!(set.len(), 1);
assert_eq!(set.processors().first().id(), 1);
}
#[test]
fn custom_filter_take_all() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 0, EfficiencyClass::Performance)),
);
let set = hw
.processors()
.to_builder()
.filter(|p| p.id() == 1)
.take_all()
.unwrap();
assert_eq!(set.len(), 1);
assert_eq!(set.processors().first().id(), 1);
}
#[test]
fn same_memory_region_filter_take() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 1, EfficiencyClass::Performance)),
);
let set = hw
.processors()
.to_builder()
.same_memory_region()
.take_all()
.unwrap();
assert_eq!(set.len(), 1);
}
#[test]
fn same_memory_region_filter_take_all() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 1, EfficiencyClass::Performance)),
);
let set = hw
.processors()
.to_builder()
.same_memory_region()
.take_all()
.unwrap();
assert_eq!(set.len(), 1);
}
#[test]
fn different_memory_region_filter_take() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 0, EfficiencyClass::Efficiency))
.processor(proc(2, 1, EfficiencyClass::Performance))
.processor(proc(3, 1, EfficiencyClass::Performance)),
);
let set = hw
.processors()
.to_builder()
.different_memory_regions()
.take_all()
.unwrap();
assert_eq!(set.len(), 2);
assert_ne!(
set.processors().first().memory_region_id(),
set.processors().last().memory_region_id()
);
}
#[test]
fn different_memory_region_filter_take_all() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 0, EfficiencyClass::Efficiency))
.processor(proc(2, 1, EfficiencyClass::Performance))
.processor(proc(3, 1, EfficiencyClass::Performance)),
);
let set = hw
.processors()
.to_builder()
.different_memory_regions()
.take_all()
.unwrap();
assert_eq!(set.len(), 2);
assert_ne!(
set.processors().first().memory_region_id(),
set.processors().last().memory_region_id()
);
}
#[test]
fn filter_combinations() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 1, EfficiencyClass::Performance))
.processor(proc(2, 1, EfficiencyClass::Efficiency)),
);
let builder = hw.processors().to_builder();
let except_set = builder.clone().filter(|p| p.id() == 0).take_all().unwrap();
let set = builder
.efficiency_processors_only()
.except(except_set.processors())
.different_memory_regions()
.take_all()
.unwrap();
assert_eq!(set.len(), 1);
assert_eq!(set.processors().first().id(), 2);
}
#[test]
fn same_memory_region_take_two_processors() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 1, EfficiencyClass::Performance))
.processor(proc(2, 1, EfficiencyClass::Efficiency)),
);
let set = hw
.processors()
.to_builder()
.same_memory_region()
.take(nz!(2))
.unwrap();
assert_eq!(set.len(), 2);
assert!(set.processors().iter().any(|p| p.id() == 1));
assert!(set.processors().iter().any(|p| p.id() == 2));
}
#[test]
fn different_memory_region_and_efficiency_class_filters() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 1, EfficiencyClass::Performance))
.processor(proc(2, 2, EfficiencyClass::Efficiency))
.processor(proc(3, 3, EfficiencyClass::Performance)),
);
let set = hw
.processors()
.to_builder()
.different_memory_regions()
.efficiency_processors_only()
.take_all()
.unwrap();
assert_eq!(set.len(), 2);
assert!(set.processors().iter().any(|p| p.id() == 0));
assert!(set.processors().iter().any(|p| p.id() == 2));
}
#[test]
fn performance_processors_but_all_efficiency() {
let hw = SystemHardware::fake(HardwareBuilder::new().processor(proc(
0,
0,
EfficiencyClass::Efficiency,
)));
let set = hw
.processors()
.to_builder()
.performance_processors_only()
.take_all();
assert!(set.is_none(), "No performance processors should be found.");
}
#[test]
fn require_different_single_region() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 0, EfficiencyClass::Efficiency)),
);
let set = hw
.processors()
.to_builder()
.different_memory_regions()
.take(nz!(2));
assert!(
set.is_none(),
"Should fail because there is not enough distinct memory regions."
);
}
#[test]
fn prefer_different_memory_regions_take_all() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 1, EfficiencyClass::Performance))
.processor(proc(2, 1, EfficiencyClass::Efficiency)),
);
let set = hw
.processors()
.to_builder()
.prefer_different_memory_regions()
.take_all()
.unwrap();
assert_eq!(set.len(), 3);
}
#[test]
fn prefer_different_memory_regions_take_n() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 1, EfficiencyClass::Performance))
.processor(proc(2, 1, EfficiencyClass::Efficiency))
.processor(proc(3, 2, EfficiencyClass::Performance)),
);
let set = hw
.processors()
.to_builder()
.prefer_different_memory_regions()
.take(nz!(2))
.unwrap();
assert_eq!(set.len(), 2);
let regions: HashSet<_> = set
.processors()
.iter()
.map(Processor::memory_region_id)
.collect();
assert_eq!(regions.len(), 2);
}
#[test]
fn prefer_same_memory_regions_take_n() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 1, EfficiencyClass::Performance))
.processor(proc(2, 1, EfficiencyClass::Efficiency))
.processor(proc(3, 2, EfficiencyClass::Performance)),
);
let set = hw
.processors()
.to_builder()
.prefer_same_memory_region()
.take(nz!(2))
.unwrap();
assert_eq!(set.len(), 2);
let regions: HashSet<_> = set
.processors()
.iter()
.map(Processor::memory_region_id)
.collect();
assert_eq!(regions.len(), 1);
}
#[test]
fn prefer_different_memory_regions_take_n_not_enough() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 1, EfficiencyClass::Performance))
.processor(proc(2, 1, EfficiencyClass::Efficiency))
.processor(proc(3, 2, EfficiencyClass::Performance)),
);
let set = hw
.processors()
.to_builder()
.prefer_different_memory_regions()
.take(nz!(4))
.unwrap();
assert_eq!(set.len(), 4);
}
#[test]
fn prefer_same_memory_regions_take_n_not_enough() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 1, EfficiencyClass::Performance))
.processor(proc(2, 1, EfficiencyClass::Efficiency))
.processor(proc(3, 2, EfficiencyClass::Performance)),
);
let set = hw
.processors()
.to_builder()
.prefer_same_memory_region()
.take(nz!(3))
.unwrap();
assert_eq!(set.len(), 3);
let regions: HashSet<_> = set
.processors()
.iter()
.map(Processor::memory_region_id)
.collect();
assert_eq!(
2,
regions.len(),
"should have picked to minimize memory regions (biggest first)"
);
}
#[test]
fn prefer_same_memory_regions_take_n_picks_best_fit() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 1, EfficiencyClass::Performance))
.processor(proc(2, 1, EfficiencyClass::Efficiency))
.processor(proc(3, 2, EfficiencyClass::Performance)),
);
let set = hw
.processors()
.to_builder()
.prefer_same_memory_region()
.take(nz!(2))
.unwrap();
assert_eq!(set.len(), 2);
let regions: HashSet<_> = set
.processors()
.iter()
.map(Processor::memory_region_id)
.collect();
assert_eq!(
1,
regions.len(),
"should have picked from memory region 1 which can accommodate the preference"
);
}
#[test]
fn take_any_returns_none_when_not_enough_processors() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 1, EfficiencyClass::Performance))
.max_processor_time(10.0),
);
let set = hw.processors().to_builder().take(nz!(3));
assert!(
set.is_none(),
"should return None when not enough processors available"
);
}
#[test]
fn take_prefer_different_returns_none_when_candidates_exhausted() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 1, EfficiencyClass::Performance))
.max_processor_time(10.0),
);
let set = hw
.processors()
.to_builder()
.prefer_different_memory_regions()
.take(nz!(3));
assert!(
set.is_none(),
"should return None when candidates exhausted in PreferDifferent mode"
);
}
#[test]
fn take_exact_returns_set_with_specified_processors() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 1, EfficiencyClass::Performance))
.processor(proc(2, 1, EfficiencyClass::Efficiency))
.max_processor_time(10.0),
);
let candidates = hw.all_processors();
let p1 = candidates.processors().first().clone();
let set = candidates.to_builder().take_exact(nonempty![p1.clone()]);
assert_eq!(set.len(), 1);
assert_eq!(set.processors().first().id(), p1.id());
}
#[test]
fn take_exact_with_multiple_processors() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 1, EfficiencyClass::Performance))
.processor(proc(2, 1, EfficiencyClass::Efficiency))
.max_processor_time(10.0),
);
let candidates = hw.all_processors();
let p0 = candidates
.processors()
.iter()
.find(|p| p.id() == 0)
.cloned()
.unwrap();
let p2 = candidates
.processors()
.iter()
.find(|p| p.id() == 2)
.cloned()
.unwrap();
let set = candidates.to_builder().take_exact(nonempty![p0, p2]);
assert_eq!(set.len(), 2);
assert!(set.processors().iter().any(|p| p.id() == 0));
assert!(set.processors().iter().any(|p| p.id() == 2));
}
#[test]
#[should_panic]
fn take_exact_panics_if_processor_not_in_candidates() {
let hw = SystemHardware::fake(
HardwareBuilder::new()
.processor(proc(0, 0, EfficiencyClass::Efficiency))
.processor(proc(1, 0, EfficiencyClass::Performance))
.max_processor_time(10.0),
);
let candidates = hw.all_processors();
let p0 = candidates
.processors()
.iter()
.find(|p| p.id() == 0)
.cloned()
.unwrap();
drop(
candidates
.to_builder()
.filter(|p| p.id() != 0)
.take_exact(nonempty![p0]),
);
}
}
#[cfg(all(test, not(miri)))] #[cfg_attr(coverage_nightly, coverage(off))]
mod tests_fallback {
use std::num::NonZero;
use new_zealand::nz;
use crate::SystemHardware;
use crate::pal::fallback::BUILD_TARGET_PLATFORM;
#[test]
fn builder_smoke_test() {
let hw = SystemHardware::fallback();
let builder = hw.processors().to_builder();
let set = builder.take_all().unwrap();
assert!(set.len() >= 1);
}
#[test]
fn take_respects_limit() {
let hw = SystemHardware::fallback();
let builder = hw.processors().to_builder();
let set = builder.take(nz!(1));
assert!(set.is_some());
assert_eq!(set.unwrap().len(), 1);
}
#[test]
fn take_all_returns_all() {
let hw = SystemHardware::fallback();
let builder = hw.processors().to_builder();
let set = builder.take_all().unwrap();
let expected_count = std::thread::available_parallelism()
.map(NonZero::get)
.unwrap_or(1);
assert_eq!(set.len(), expected_count);
}
#[test]
fn performance_only_filter() {
let hw = SystemHardware::fallback();
let builder = hw.processors().to_builder();
let set = builder.performance_processors_only().take_all().unwrap();
let expected_count = std::thread::available_parallelism()
.map(NonZero::get)
.unwrap_or(1);
assert_eq!(set.len(), expected_count);
}
#[test]
fn same_memory_region_filter() {
let hw = SystemHardware::fallback();
let builder = hw.processors().to_builder();
let set = builder.same_memory_region().take_all().unwrap();
let expected_count = std::thread::available_parallelism()
.map(NonZero::get)
.unwrap_or(1);
assert_eq!(set.len(), expected_count);
for processor in set.processors() {
assert_eq!(processor.memory_region_id(), 0);
}
}
#[test]
fn except_filter() {
let hw = SystemHardware::fallback();
let builder = hw.processors().to_builder();
if BUILD_TARGET_PLATFORM.processor_count() > 1 {
let except_set = builder.clone().filter(|p| p.id() == 0).take_all().unwrap();
assert_eq!(except_set.len(), 1);
let set = builder.except(except_set.processors()).take_all().unwrap();
for processor in set.processors() {
assert_ne!(processor.id(), 0);
}
}
}
#[test]
fn enforce_resource_quota() {
let hw = SystemHardware::fallback();
let builder = hw.all_processors().to_builder();
let set = builder.enforce_resource_quota().take_all().unwrap();
assert!(set.len() >= 1);
}
#[test]
fn where_available_for_current_thread() {
use std::thread;
thread::spawn(|| {
let hw = SystemHardware::fallback();
let one = hw.processors().to_builder().take(nz!(1)).unwrap();
one.pin_current_thread_to();
let set = hw
.processors()
.to_builder()
.where_available_for_current_thread()
.take_all();
assert!(set.is_some());
assert_eq!(set.unwrap().len(), 1);
})
.join()
.unwrap();
}
#[test]
fn quota_not_enforced_by_default_on_builder() {
let hw = SystemHardware::fallback();
let builder = hw.all_processors().to_builder();
let set = builder.take_all().unwrap();
let expected_count = std::thread::available_parallelism()
.map(NonZero::get)
.unwrap_or(1);
assert_eq!(set.len(), expected_count);
}
}