diff --git a/Cargo.lock b/Cargo.lock index a6a3578..7efc37b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -103,6 +103,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -173,6 +182,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 = "anymap3" version = "1.0.1" @@ -334,12 +355,15 @@ name = "autobats" version = "0.1.0" dependencies = [ "bevy", + "criterion", "dirs", "include_dir", "ordered-float", + "rand", + "rand_hc", + "rstar", "rusqlite", "rusqlite_migration", - "spart", "steel-core", ] @@ -1365,7 +1389,7 @@ dependencies = [ "crossbeam-queue", "derive_more", "futures-lite", - "heapless", + "heapless 0.9.2", "pin-project", ] @@ -1767,6 +1791,12 @@ dependencies = [ "wayland-client", ] +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "castaway" version = "0.2.4" @@ -1826,6 +1856,33 @@ 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 = "clang-sys" version = "1.8.1" @@ -1837,6 +1894,31 @@ dependencies = [ "libloading", ] +[[package]] +name = "clap" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +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 = "codegen" version = "0.2.0" @@ -2112,6 +2194,40 @@ 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", + "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 = "critical-section" version = "1.2.0" @@ -2127,6 +2243,25 @@ 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-queue" version = "0.3.12" @@ -2917,6 +3052,16 @@ dependencies = [ "hashbrown 0.15.5", ] +[[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 = "heapless" version = "0.9.2" @@ -4010,6 +4155,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 = "option-ext" version = "0.2.0" @@ -4044,6 +4195,16 @@ dependencies = [ "ttf-parser", ] +[[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 = "parking" version = "2.2.1" @@ -4343,6 +4504,15 @@ dependencies = [ "rand", ] +[[package]] +name = "rand_hc" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54fc7b35e3026136eaf1decdc66ecde3efadfd663cc0d71115ad40da7ebcff63" +dependencies = [ + "rand_core 0.9.5", +] + [[package]] name = "rand_xoshiro" version = "0.6.0" @@ -4370,6 +4540,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 = "read-fonts" version = "0.35.0" @@ -4500,6 +4690,17 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" +[[package]] +name = "rstar" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb" +dependencies = [ + "heapless 0.8.0", + "num-traits", + "smallvec", +] + [[package]] name = "rusqlite" version = "0.37.0" @@ -4795,16 +4996,6 @@ dependencies = [ "serde", ] -[[package]] -name = "spart" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a05d85e94c16e35b2f963dee2eaa6f6b2494ee5dad740b045b139c5614872d6" -dependencies = [ - "ordered-float", - "tracing", -] - [[package]] name = "spin" version = "0.10.0" @@ -5120,6 +5311,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" diff --git a/Cargo.toml b/Cargo.toml index 34350fc..dbcd962 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,12 +5,13 @@ edition = "2024" [dependencies] bevy = { version = "0.18", default-features = false, features = ["2d"] } +criterion = { version = "0.8.1", default-features = false, features = ["cargo_bench_support", "rayon"] } dirs = "6.0.0" include_dir = "0.7.4" ordered-float = "5.1.0" +rstar = "0.12.2" rusqlite = { version = "0.37", default-features = false, features = ["bundled", "blob", "functions", "jiff"] } rusqlite_migration = { version = "2.3.0", features = ["from-directory"] } -spart = "0.5.0" steel-core = { git="https://github.com/mattwparas/steel.git", branch = "master" } # Enable a small amount of optimization in the dev profile. @@ -20,3 +21,11 @@ opt-level = 1 # Enable a large amount of optimization in the dev profile for dependencies. [profile.dev.package."*"] opt-level = 3 + +[dev-dependencies] +rand = "0.9.2" +rand_hc = "0.4.0" + +[[bench]] +name = "benchmarks" +harness = false diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs new file mode 100644 index 0000000..5071884 --- /dev/null +++ b/benches/benchmarks.rs @@ -0,0 +1,102 @@ +use rand::{Rng, SeedableRng}; +use rand_hc::Hc128Rng; + +use autobats::geom::Point; +use criterion::{criterion_group, criterion_main, Criterion}; +use rstar::{RStarInsertionStrategy, RTree, RTreeParams}; + +const SEED_1: &[u8; 32] = b"Gv0aHMtHkBGsUXNspGU9fLRuCWkZWHZx"; +const SEED_2: &[u8; 32] = b"km7DO4GeaFZfTcDXVpnO7ZJlgUY7hZiS"; + +struct Params; + +impl RTreeParams for Params { + const MIN_SIZE: usize = 2; + const MAX_SIZE: usize = 30; + const REINSERTION_COUNT: usize = 1; + type DefaultInsertionStrategy = RStarInsertionStrategy; +} + +const DEFAULT_BENCHMARK_TREE_SIZE: usize = 50_000; + +fn bulk_load_baseline(c: &mut Criterion) { + c.bench_function("bulk load baseline", move |b| { + let points: Vec<_> = create_random_points(DEFAULT_BENCHMARK_TREE_SIZE, SEED_1); + + b.iter(|| { + RTree::<_, Params>::bulk_load_with_params(points.clone()); + }); + }); +} + +fn bulk_load_comparison(c: &mut Criterion) { + c.bench_function("insert sequential", |b| { + let points: Vec<_> = create_random_points(DEFAULT_BENCHMARK_TREE_SIZE, SEED_1); + b.iter(move || { + let mut rtree = rstar::RTree::new(); + for point in &points { + rtree.insert(*point); + } + }); + }); +} + +fn tree_creation_quality(c: &mut Criterion) { + const SIZE: usize = 100_000; + let points: Vec<_> = create_random_points(SIZE, SEED_1); + let tree_bulk_loaded = RTree::<_, Params>::bulk_load_with_params(points.clone()); + let mut tree_sequential = RTree::new(); + for point in &points { + tree_sequential.insert(*point); + } + + let query_points = create_random_points(100_000, SEED_2); + let query_points_cloned_1 = query_points.clone(); + c.bench_function("bulk load quality", move |b| { + b.iter(|| { + for query_point in &query_points { + tree_bulk_loaded.nearest_neighbor(query_point).unwrap(); + } + }) + }) + .bench_function("sequential load quality", move |b| { + b.iter(|| { + for query_point in &query_points_cloned_1 { + tree_sequential.nearest_neighbor(query_point).unwrap(); + } + }); + }); +} + +fn range_query(c: &mut Criterion) { + const SIZE: usize = 50_000; + let points: Vec<_> = create_random_points(SIZE, SEED_1); + let tree = RTree::<_, Params>::bulk_load_with_params(points.clone()); + + c.bench_function("range query", move |b| { + let d = 30.0f32.powi(2); + let mut i = 0; + b.iter(|| { + let q = &points[i]; + i = (i + 1) % SIZE; + tree.locate_within_distance(*q, d) + }) + }); +} + +criterion_group!( + benches, + //bulk_load_baseline, + // bulk_load_comparison, + // tree_creation_quality, + range_query +); +criterion_main!(benches); + +fn create_random_points(num_points: usize, seed: &[u8; 32]) -> Vec { + let mut rng = Hc128Rng::from_seed(*seed); + let r = (-10_000.0)..=10_000.0; + (0..num_points) + .map(|_| Point::new(rng.random_range(r.clone()), rng.random_range(r.clone()))) + .collect() +} diff --git a/src/geom.rs b/src/geom.rs index c436045..deafe05 100644 --- a/src/geom.rs +++ b/src/geom.rs @@ -2,14 +2,54 @@ use std::cmp::Ordering; use bevy::prelude::*; use ordered_float::OrderedFloat; -use spart::{geometry::BoundingVolume, rstar_tree::RStarTreeObject}; +use rstar::Point as PointTrait; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub struct Point { pub point: Vec2, pub entity: Entity, } +impl Point { + pub fn new(x: f32, y: f32) -> Self { + Self { + point: Vec2::new(x, y), + entity: Entity::PLACEHOLDER, + } + } +} + +impl From<[f32; 2]> for Point { + fn from(value: [f32; 2]) -> Self { + Self { + point: value.into(), + entity: Entity::PLACEHOLDER, + } + } +} + +impl PointTrait for Point { + type Scalar = f32; + + const DIMENSIONS: usize = 2; + + fn generate(mut generator: impl FnMut(usize) -> Self::Scalar) -> Self { + let point = Vec2::new(generator(0), generator(1)); + Point { + point, + entity: Entity::PLACEHOLDER, + } + } + + fn nth(&self, index: usize) -> Self::Scalar { + self.point[index] + } + + fn nth_mut(&mut self, index: usize) -> &mut Self::Scalar { + &mut self.point[index] + } +} + impl PartialEq for Point { fn eq(&self, other: &Self) -> bool { OrderedFloat(self.point.x) == OrderedFloat(other.point.x) @@ -28,36 +68,3 @@ impl PartialOrd for Point { } } } - -impl RStarTreeObject for Point { - type B = PointBox; - - fn mbr(&self) -> Self::B { - todo!() - } -} - -#[derive(Debug, Clone)] -pub struct PointBox; - -impl BoundingVolume for PointBox { - fn area(&self) -> f64 { - todo!() - } - - fn union(&self, other: &Self) -> Self { - todo!() - } - - fn intersects(&self, other: &Self) -> bool { - todo!() - } - - fn overlap(&self, other: &Self) -> f64 { - todo!() - } - - fn margin(&self) -> f64 { - todo!() - } -} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..60d1429 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,2 @@ +pub mod db; +pub mod geom; diff --git a/src/main.rs b/src/main.rs index 672a0b5..ca7406b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,5 @@ +use autobats::db::init_db; use bevy::prelude::*; -use db::init_db; - -mod db; -mod geom; fn main() { App::new()