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:
237
src-tauri/Cargo.lock
generated
237
src-tauri/Cargo.lock
generated
@@ -43,6 +43,15 @@ dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alloca"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android_log-sys"
|
||||
version = "0.3.2"
|
||||
@@ -69,6 +78,18 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anes"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.100"
|
||||
@@ -489,6 +510,12 @@ dependencies = [
|
||||
"toml 0.9.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.49"
|
||||
@@ -552,6 +579,58 @@ dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ciborium"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"ciborium-ll",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-io"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-ll"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"half",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
@@ -651,6 +730,41 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d883447757bb0ee46f233e9dc22eb84d93a9508c9b868687b274fc431d886bf"
|
||||
dependencies = [
|
||||
"alloca",
|
||||
"anes",
|
||||
"cast",
|
||||
"ciborium",
|
||||
"clap",
|
||||
"criterion-plot",
|
||||
"itertools 0.13.0",
|
||||
"num-traits",
|
||||
"oorandom",
|
||||
"page_size",
|
||||
"plotters",
|
||||
"rayon",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tinytemplate",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion-plot"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed943f81ea2faa8dcecbbfa50164acf95d555afec96a27871663b300e387b2e4"
|
||||
dependencies = [
|
||||
"cast",
|
||||
"itertools 0.13.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.15"
|
||||
@@ -660,12 +774,37 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
@@ -1523,6 +1662,17 @@ dependencies = [
|
||||
"syn 2.0.111",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
@@ -1883,6 +2033,15 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
@@ -2509,6 +2668,12 @@ version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
version = "11.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
||||
|
||||
[[package]]
|
||||
name = "open"
|
||||
version = "5.3.3"
|
||||
@@ -2537,6 +2702,16 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "page_size"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pango"
|
||||
version = "0.18.3"
|
||||
@@ -2779,6 +2954,34 @@ dependencies = [
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"plotters-backend",
|
||||
"plotters-svg",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plotters-backend"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
|
||||
|
||||
[[package]]
|
||||
name = "plotters-svg"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
|
||||
dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.17.16"
|
||||
@@ -3070,6 +3273,26 @@ version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.18"
|
||||
@@ -3224,10 +3447,12 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"criterion",
|
||||
"docx-rs",
|
||||
"itertools",
|
||||
"itertools 0.14.0",
|
||||
"log",
|
||||
"rand 0.9.2",
|
||||
"rayon",
|
||||
"rstest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4272,6 +4497,16 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinytemplate"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.10.0"
|
||||
|
||||
@@ -30,3 +30,19 @@ log = "0.4.29"
|
||||
rand = "0.9.2"
|
||||
docx-rs = "0.4.18"
|
||||
anyhow = "1.0.100"
|
||||
rayon = "1.11"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.8.1", features = ["html_reports"] }
|
||||
|
||||
[[bench]]
|
||||
name = "rayon"
|
||||
harness = false
|
||||
|
||||
[lints.clippy]
|
||||
indexing_slicing = "deny"
|
||||
fallible_impl_from = "deny"
|
||||
wildcard_enum_match_arm = "deny"
|
||||
unneeded_field_pattern = "deny"
|
||||
fn_params_excessive_bools = "deny"
|
||||
# must_use_candidate = "deny"
|
||||
|
||||
53
src-tauri/benches/rayon.rs
Normal file
53
src-tauri/benches/rayon.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use std::hint::black_box;
|
||||
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use rota_lib::{
|
||||
config::{ToxicPair, UserConfig},
|
||||
resident::Resident,
|
||||
schedule::{MonthlySchedule, ShiftType},
|
||||
scheduler::Scheduler,
|
||||
slot::Day,
|
||||
workload::WorkloadTracker,
|
||||
};
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let config = maximal_config();
|
||||
let scheduler = Scheduler::new_with_config(config);
|
||||
|
||||
c.bench_function("scheduler run", |b| {
|
||||
b.iter(|| {
|
||||
let mut schedule = MonthlySchedule::new();
|
||||
let mut tracker = WorkloadTracker::default();
|
||||
let result = scheduler.run(&mut schedule, &mut tracker);
|
||||
black_box(result)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn maximal_config() -> UserConfig {
|
||||
UserConfig::default()
|
||||
.with_holidays(vec![2, 3, 10, 11, 12, 25])
|
||||
.with_residents(vec![
|
||||
Resident::new(1, "R1").with_max_shifts(3),
|
||||
Resident::new(2, "R2").with_max_shifts(4),
|
||||
Resident::new(3, "R3").with_reduced_load(),
|
||||
Resident::new(4, "R4").with_allowed_types(vec![ShiftType::Closed]),
|
||||
Resident::new(5, "R5")
|
||||
.with_allowed_types(vec![ShiftType::OpenFirst, ShiftType::OpenSecond]),
|
||||
Resident::new(6, "R6").with_negative_shifts(vec![Day(5), Day(15), Day(25)]),
|
||||
Resident::new(7, "R7")
|
||||
.with_allowed_types(vec![ShiftType::OpenFirst, ShiftType::OpenSecond]),
|
||||
Resident::new(8, "R8"),
|
||||
Resident::new(9, "R9"),
|
||||
Resident::new(10, "R10").with_reduced_load(),
|
||||
])
|
||||
.with_toxic_pairs(vec![
|
||||
ToxicPair::new(1, 2),
|
||||
ToxicPair::new(3, 4),
|
||||
ToxicPair::new(3, 5),
|
||||
ToxicPair::new(7, 8),
|
||||
])
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +113,16 @@ impl Default for Slot {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for Slot {
|
||||
fn from(value: u8) -> Self {
|
||||
let mut slot = Slot::default();
|
||||
for _ in 0..value {
|
||||
slot = slot.next()
|
||||
}
|
||||
slot
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, PartialOrd, Ord, Eq, Debug, Hash, Clone, Copy)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Day(pub u8);
|
||||
|
||||
Reference in New Issue
Block a user