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:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user