From b7c1f192049c8b1c4bd29bba37dd63cde35909c2 Mon Sep 17 00:00:00 2001 From: Joe Date: Fri, 17 Oct 2025 12:55:48 -0700 Subject: [PATCH] replaced atomics with a vec-wide lock, now 2x slower than before --- Cargo.lock | 58 +----------------- Cargo.toml | 4 ++ benches/benchmarks.rs | 134 +--------------------------------------- src/lib.rs | 139 ++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 43 +++++++++++++ 5 files changed, 189 insertions(+), 189 deletions(-) create mode 100644 src/lib.rs create mode 100644 src/main.rs diff --git a/Cargo.lock b/Cargo.lock index a4dacd5..aa2316a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -79,12 +79,6 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "cast" version = "0.3.0" @@ -250,25 +244,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "hash32" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" -dependencies = [ - "byteorder", -] - -[[package]] -name = "heapless" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" -dependencies = [ - "hash32", - "stable_deref_trait", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -306,12 +281,6 @@ version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" -[[package]] -name = "libm" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" - [[package]] name = "log" version = "0.4.28" @@ -331,7 +300,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -500,17 +468,6 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" -[[package]] -name = "rstar" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb" -dependencies = [ - "heapless", - "num-traits", - "smallvec", -] - [[package]] name = "rustversion" version = "1.0.22" @@ -576,28 +533,15 @@ dependencies = [ ] [[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "spacebench" +name = "spacedog" version = "0.1.0" dependencies = [ "clap", "criterion", "rand", "rand_hc", - "rstar", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - [[package]] name = "strsim" version = "0.11.1" diff --git a/Cargo.toml b/Cargo.toml index 6935f49..47aebee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,7 @@ rand_hc = "0.4.0" [[bench]] name = "benchmarks" harness = false + +[dependencies] +rand = "0.9.2" +rand_hc = "0.4.0" diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index db42a47..b7bf739 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -1,134 +1,4 @@ -use std::{ - sync::{ - atomic::{AtomicU64, AtomicUsize, Ordering::Relaxed}, - Arc, RwLock, - }, - thread, -}; - -struct Point2d { - x: f64, - y: f64, -} - -#[derive(Debug, Default, Clone, Copy)] -struct PVal { - point: usize, - val: f64, -} - -impl PartialEq for PVal { - fn eq(&self, other: &Self) -> bool { - self.val == other.val - } -} - -impl Eq for PVal {} - -impl PartialOrd for PVal { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for PVal { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.val.partial_cmp(&other.val).unwrap() - } -} - -struct Spindex2d { - xpoints: Arc>, - ypoints: Arc>, - xs: Arc>>, - ys: Arc>>, -} - -impl Spindex2d { - pub fn new(points: &[Point2d]) -> Self { - let mut xpoints = Vec::with_capacity(points.len()); - let mut ypoints = Vec::with_capacity(points.len()); - let mut xs = Vec::with_capacity(points.len()); - let mut ys = Vec::with_capacity(points.len()); - for (i, point) in points.iter().enumerate() { - let x = PVal { - point: i, - val: point.x, - }; - xs.push(x); - let y = PVal { - point: i, - val: point.y, - }; - ys.push(y); - - xpoints.push(AtomicUsize::new(i)); - ypoints.push(AtomicUsize::new(i)); - } - - let index = Self { - xpoints: xpoints.into(), - ypoints: ypoints.into(), - xs: Arc::new(RwLock::new(xs)), - ys: Arc::new(RwLock::new(ys)), - }; - - index.sort(); - - index - } - - /// This method assumes that the points have the same cardinality and order as the points used - /// when constructing this index. May panic if the updated points argument is longer than the - /// original points. - pub fn update(&self, points: &[Point2d]) { - let mut xs = self.xs.write().unwrap(); - let mut ys = self.ys.write().unwrap(); - for (i, point) in points.iter().enumerate() { - let xi = self.xpoints[i].load(Relaxed); - let yi = self.ypoints[i].load(Relaxed); - let x = PVal { - point: i, - val: point.x, - }; - xs[xi] = x; - - let y = PVal { - point: i, - val: point.y, - }; - ys[yi] = y; - } - self.sort(); - } - - fn sort(&self) { - let xs = self.xs.clone(); - let points = self.xpoints.clone(); - let xhandle = thread::spawn(move || { - let mut xs = xs.write().unwrap(); - xs.sort_unstable(); - for (i, x) in xs.iter().enumerate() { - let p = x.point; - points[p].store(i, Relaxed); - } - }); - - let ys = self.ys.clone(); - let points = self.ypoints.clone(); - let yhandle = thread::spawn(move || { - let mut ys = ys.write().unwrap(); - ys.sort_unstable(); - for (i, y) in ys.iter().enumerate() { - let p = y.point; - points[p].store(i, Relaxed); - } - }); - - xhandle.join().unwrap(); - yhandle.join().unwrap(); - } -} +use spacedog::*; use rand::{Rng, SeedableRng}; use rand_hc::Hc128Rng; @@ -159,7 +29,7 @@ fn new_index(c: &mut Criterion) { }); } -fn update_positions(c: &mut Criterion) { +fn _update_positions(c: &mut Criterion) { c.bench_function("update positions", move |b| { let points: Vec<_> = create_random_points(DEFAULT_NUM_POINTS, SEED_1) .into_iter() diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..a1cd9d5 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,139 @@ +use std::{ + sync::{ + atomic::{AtomicUsize, Ordering::Relaxed}, + Arc, RwLock, + }, + thread, +}; + +#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)] +pub struct Point2d { + pub x: f64, + pub y: f64, +} + +#[derive(Debug, Default, Clone, Copy)] +pub struct PVal { + point: usize, + val: f64, +} + +impl PartialEq for PVal { + fn eq(&self, other: &Self) -> bool { + self.val == other.val + } +} + +impl Eq for PVal {} + +impl PartialOrd for PVal { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for PVal { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.val.partial_cmp(&other.val).unwrap() + } +} + +#[derive(Debug, Default, Clone)] +pub struct Spindex2d { + xpoints: Arc>>, + ypoints: Arc>>, + xs: Arc>>, + ys: Arc>>, +} + +impl Spindex2d { + pub fn new(points: &[Point2d]) -> Self { + let mut xpoints = Vec::with_capacity(points.len()); + let mut ypoints = Vec::with_capacity(points.len()); + let mut xs = Vec::with_capacity(points.len()); + let mut ys = Vec::with_capacity(points.len()); + for (i, point) in points.iter().enumerate() { + let x = PVal { + point: i, + val: point.x, + }; + xs.push(x); + let y = PVal { + point: i, + val: point.y, + }; + ys.push(y); + + xpoints.push(i); + ypoints.push(i); + } + + let index = Self { + xpoints: Arc::new(RwLock::new(xpoints)), + ypoints: Arc::new(RwLock::new(ypoints)), + xs: Arc::new(RwLock::new(xs)), + ys: Arc::new(RwLock::new(ys)), + }; + + index.sort(); + + index + } + + /// This method assumes that the points have the same cardinality and order as the points used + /// when constructing this index. May panic if the updated points argument is longer than the + /// original points. + pub fn update(&self, points: &[Point2d]) { + let mut xs = self.xs.write().unwrap(); + let mut ys = self.ys.write().unwrap(); + let xpoints = self.xpoints.read().unwrap(); + let ypoints = self.ypoints.read().unwrap(); + for (i, point) in points.iter().enumerate() { + println!("hello: {i}"); + let xi = xpoints[i]; + let yi = ypoints[i]; + let x = PVal { + point: i, + val: point.x, + }; + xs[xi] = x; + + let y = PVal { + point: i, + val: point.y, + }; + ys[yi] = y; + } + println!("done updating points"); + self.sort(); + } + + fn sort(&self) { + let xs = self.xs.clone(); + let points = self.xpoints.clone(); + let xhandle = thread::spawn(move || { + let mut xs = xs.write().unwrap(); + xs.sort_unstable(); + let mut points = points.write().unwrap(); + for (i, x) in xs.iter().enumerate() { + let p = x.point; + points[p] = i; + } + }); + + let ys = self.ys.clone(); + let points = self.ypoints.clone(); + let yhandle = thread::spawn(move || { + let mut ys = ys.write().unwrap(); + ys.sort_unstable(); + let mut points = points.write().unwrap(); + for (i, y) in ys.iter().enumerate() { + let p = y.point; + points[p] = i; + } + }); + + xhandle.join().unwrap(); + yhandle.join().unwrap(); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..5dc2e72 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,43 @@ +use rand::{Rng, SeedableRng}; +use rand_hc::Hc128Rng; + +use spacedog::*; + +fn create_random_points(num_points: usize, seed: &[u8; 32]) -> Vec<[f64; 2]> { + let mut rng = Hc128Rng::from_seed(*seed); + (0..num_points).map(|_| rng.random()).collect() +} + +const SEED_1: &[u8; 32] = b"Gv0aHMtHkBGsUXNspGU9fLRuCWkZWHZx"; +// const SEED_2: &[u8; 32] = b"km7DO4GeaFZfTcDXVpnO7ZJlgUY7hZiS"; + +const DEFAULT_NUM_POINTS: usize = 1_000_000; + +fn main() { + let points: Vec<_> = create_random_points(DEFAULT_NUM_POINTS, SEED_1) + .into_iter() + .map(|[x, y]| Point2d { x, y }) + .collect(); + + let mut rng = Hc128Rng::from_seed(*SEED_1); + + let jitters: Vec<_> = points + .iter() + .map(|p| { + let x = p.x + rng.random_range(-5.0..=5.0); + let y = p.y + rng.random_range(-5.0..=5.0); + Point2d { x, y } + }) + .collect(); + + let index = Spindex2d::new(&points); + + for round in 0..100 { + dbg!(round); + if round % 2 == 0 { + index.update(&jitters); + } else { + index.update(&points); + } + } +}