Parallelize DFS, use an AtomicBool for short-circuiting

time:   [73.407 µs 76.145 µs 79.345 µs]
change: [−40.126% −24.679% −3.8062%] (p = 0.04 < 0.05)
This commit is contained in:
2026-02-01 22:53:04 +02:00
parent c9e1d3ab67
commit 98cb9f7f3e
6 changed files with 375 additions and 5 deletions

View File

@@ -1,3 +1,5 @@
use std::sync::atomic::{AtomicBool, Ordering};
use crate::{
config::UserConfig,
resident::ResidentId,
@@ -7,7 +9,10 @@ use crate::{
workload::{WorkloadBounds, WorkloadTracker},
};
use anyhow::bail;
use log::warn;
use rand::Rng;
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
pub struct Scheduler {
pub config: UserConfig,
@@ -46,7 +51,46 @@ impl Scheduler {
//TODO: add validation
self.search(schedule, tracker, Slot::default())
// find first non-manually-filled slot
let slot = (0..=self.config.total_slots)
.find(|&slot_idx| !schedule.0.contains_key(&Slot::from(slot_idx)))
.map(Slot::from)
.ok_or_else(|| anyhow::anyhow!("Schedule is already full"))?;
let resident_ids = self.valid_residents(slot, schedule);
let solved_in_thread = AtomicBool::new(false);
let solved_schedule = resident_ids.par_iter().find_map_any(|&id| {
let mut local_schedule = schedule.clone();
let mut local_tracker = tracker.clone();
local_schedule.insert(slot, id);
local_tracker.insert(id, &self.config, slot);
let solved = self.search(
&mut local_schedule,
&mut local_tracker,
slot.next(),
&solved_in_thread,
);
match solved {
Ok(true) => Some(local_schedule),
Ok(false) => None,
Err(e) => {
warn!("Search error: {}", e);
None
}
}
});
// TODO: can return the schedule instead of a bool
if let Some(solved_schedule) = solved_schedule {
*schedule = solved_schedule;
return Ok(true);
}
Ok(false)
}
/// DFS where maximum depth is calculated by total_days_of_month + odd_days_of_month each node is called a slot
@@ -57,7 +101,12 @@ impl Scheduler {
schedule: &mut MonthlySchedule,
tracker: &mut WorkloadTracker,
slot: Slot,
solved_in_thread: &AtomicBool,
) -> anyhow::Result<bool> {
if solved_in_thread.load(Ordering::Relaxed) {
bail!("Another thread found the solution")
}
if self.timer.limit_exceeded() {
anyhow::bail!("Time exceeded. Restrictions too tight");
}
@@ -69,11 +118,15 @@ impl Scheduler {
}
if slot.greater_than(self.config.total_days) {
return Ok(tracker.are_all_thresholds_met(&self.config, &self.bounds));
if tracker.are_all_thresholds_met(&self.config, &self.bounds) {
solved_in_thread.store(true, Ordering::Relaxed);
return Ok(true);
}
return Ok(false);
}
if schedule.is_slot_manually_assigned(&slot) {
return self.search(schedule, tracker, slot.next());
return self.search(schedule, tracker, slot.next(), solved_in_thread);
}
// sort candidates by current workload, add rng for tie breakers
@@ -88,7 +141,7 @@ impl Scheduler {
schedule.insert(slot, id);
tracker.insert(id, &self.config, slot);
if self.search(schedule, tracker, slot.next())? {
if self.search(schedule, tracker, slot.next(), solved_in_thread)? {
return Ok(true);
}