From 528665a24b2b70b9b703846add6aa920389d68ea Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Sun, 11 Feb 2024 16:25:46 +0100 Subject: [PATCH] Fix bugs in unify function --- Cargo.lock | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 9 +++- src/lib.rs | 76 ++++++++++++++++++++++++++++----- 3 files changed, 194 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f82fcf7..fc1cd9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,126 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +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.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rayon" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "uf_rush" version = "0.1.1" +dependencies = [ + "rand", + "rayon", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/Cargo.toml b/Cargo.toml index a93d5ee..a9bc7a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,11 @@ edition = "2021" license = "MIT" readme = "README.md" repository = "https://github.com/khojasteh/uf_rush" -documentation = "https://docs.rs/uf_rush/0.1.1" \ No newline at end of file +documentation = "https://docs.rs/uf_rush/0.1.1" + +[dev-dependencies] +rand = "0.8.5" +rayon = "1.8.1" + +[profile.test] +opt-level = 3 diff --git a/src/lib.rs b/src/lib.rs index 8b945f4..476107a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -158,40 +158,52 @@ impl UFRush { /// deep, which helps keep the operation's time complexity nearly constant. pub fn unite(&self, x: usize, y: usize) -> bool { loop { + // Load representative for x and y let mut x_rep = self.find(x); let mut y_rep = self.find(y); + // If they are already part of the same set, return false if x_rep == y_rep { return false; } + // Load the encoded representation of the representatives let x_node = self.nodes[x_rep].load(Ordering::Relaxed); let y_node = self.nodes[y_rep].load(Ordering::Relaxed); let mut x_rank = rank(x_node); let mut y_rank = rank(y_node); - if x_rank > y_rank || (x_rank == y_rank && x_rank < y_rank) { + // Swap the elements around to always make x the smaller one + if x_rank > y_rank || (x_rank == y_rank && x_rep > y_rep) { std::mem::swap(&mut x_rep, &mut y_rep); std::mem::swap(&mut x_rank, &mut y_rank); } + // x_rep is a root let cur_value = encode(x_rep, x_rank); + // assign the new root to be y let new_value = encode(y_rep, x_rank); + // change the value of the smaller subtree root to point to the other one if self.nodes[x_rep] .compare_exchange(cur_value, new_value, Ordering::Release, Ordering::Acquire) .is_ok() { - let cur_value = encode(y_rep, y_rank); - let new_value = encode(y_rep, y_rank + 1); - let _ = self.nodes[y_rep].compare_exchange_weak( - cur_value, - new_value, - Ordering::Release, - Ordering::Relaxed, - ); + // x_repr now points to y_repr + // If the subtrees has the same height, increase the rank of the new root + if x_rank == y_rank { + let cur_value = encode(y_rep, y_rank); + let new_value = encode(y_rep, y_rank + 1); + let _ = self.nodes[y_rep].compare_exchange_weak( + cur_value, + new_value, + Ordering::Release, + Ordering::Relaxed, + ); + } return true; } + // A different thread has already merged modified the value of x_repr -> repeat } } @@ -313,6 +325,50 @@ mod tests { assert!(!is_cyclic(4, [(0, 1), (1, 2), (2, 3)])); } + #[test] + fn stress_test() { + use rand::prelude::*; + use std::sync::{Arc, Barrier}; + use std::thread; + + let num_elements = 1_00_000; // Adjust based on the system's capability + + let elements = 1 << 9; + + // Preparing a pool of element pairs for unification + let mut pairs = Vec::new(); + // Make sure everythin is connected + for i in 0..=elements { + let i = i % elements; + pairs.push((i, i + 1)); + } + // Add random edges to the graph + for i in 0..num_elements - 1 { + let source = rand::random::() % elements; + let target = rand::random::() % elements; + pairs.push((source, target)); + } + + for i in 0..1000 { + // Shuffle pairs to randomize access patterns + let uf = Arc::new(UFRush::new(elements + 1)); + use rand::{thread_rng, Rng}; + use rayon::prelude::*; + let mut rng = thread_rng(); + let total_unites = AtomicUsize::new(0); + let total_unites = &total_unites; + pairs.shuffle(&mut rng); + + pairs.par_iter().for_each(|(x, y)| { + if uf.unite(*x, *y) { + total_unites.fetch_add(1, Ordering::Relaxed); + } + }); + + assert_eq!(total_unites.load(Ordering::SeqCst), elements); + } + } + fn is_cyclic(vertices: usize, edges: I) -> bool where I: IntoIterator, @@ -340,4 +396,4 @@ mod tests { // Wait for all threads to finish and check if any of them found a cycle handles.into_iter().any(|handle| handle.join().unwrap()) } -} \ No newline at end of file +}