From e309749c172ce46262f994d3230b71c987741203 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Tue, 2 Jan 2024 13:23:35 +0100 Subject: [PATCH] Primary caching 2: Introduce `FlatVecDeque` (#4593) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce `FlatVecDeque` (feel free to rename), the core datastructure behind the primary cache. You can view it as a "native", circular `ListArray`: it's a flattened array of arrays, implemented as two ringbuffers, that stores actual components (i.e. deserialized data) in a cache friendly way. Also plenty of APIs to get data in and out, in and out of order. Once again, these benchmarks are disabled on CI. Some numbers for posterity / git log (5950X, Arch): ``` flat_vec_deque/insert/empty 1.00 310.9±3.87ns 3.0 GElem/sec flat_vec_deque/insert/prefilled/back 1.00 28.6±0.20µs 33.4 MElem/sec flat_vec_deque/insert/prefilled/front 1.00 29.1±0.17µs 32.7 MElem/sec flat_vec_deque/insert/prefilled/middle 1.00 42.8±0.57µs 22.3 MElem/sec flat_vec_deque/insert_range/empty 1.00 3.5±0.04µs 1348.1 MElem/sec flat_vec_deque/insert_range/prefilled/back 1.00 31.9±0.20µs 149.4 MElem/sec flat_vec_deque/insert_range/prefilled/front 1.00 30.6±0.17µs 155.7 MElem/sec flat_vec_deque/insert_range/prefilled/middle 1.00 46.3±0.20µs 102.9 MElem/sec flat_vec_deque/insert_with/empty 1.00 1375.4±43.80ns 3.4 GElem/sec flat_vec_deque/insert_with/prefilled/back 1.00 30.1±0.16µs 158.6 MElem/sec flat_vec_deque/insert_with/prefilled/front 1.00 27.8±0.83µs 171.5 MElem/sec flat_vec_deque/insert_with/prefilled/middle 1.00 44.8±0.34µs 106.4 MElem/sec flat_vec_deque/range/prefilled/back 1.00 15.3±0.05µs 312.3 MElem/sec flat_vec_deque/range/prefilled/front 1.00 15.8±0.15µs 301.2 MElem/sec flat_vec_deque/range/prefilled/middle 1.00 14.8±0.09µs 323.2 MElem/sec flat_vec_deque/remove/prefilled/back 1.00 14.4±0.07µs 67.8 KElem/sec flat_vec_deque/remove/prefilled/front 1.00 28.5±0.28µs 34.2 KElem/sec flat_vec_deque/remove/prefilled/middle 1.00 28.5±0.11µs 34.3 KElem/sec flat_vec_deque/remove_range/prefilled/back 1.00 14.6±0.07µs 326.0 MElem/sec flat_vec_deque/remove_range/prefilled/front 1.00 28.6±0.16µs 166.7 MElem/sec flat_vec_deque/remove_range/prefilled/middle 1.00 29.3±0.17µs 162.8 MElem/sec ``` --- Part of the primary caching series of PR (index search, joins, deserialization): - #4592 - #4593 --- Cargo.lock | 16 + Cargo.toml | 1 + crates/re_query_cache/Cargo.toml | 49 + .../re_query_cache/benches/flat_vec_deque.rs | 333 +++++++ crates/re_query_cache/src/flat_vec_deque.rs | 866 ++++++++++++++++++ crates/re_query_cache/src/lib.rs | 9 + 6 files changed, 1274 insertions(+) create mode 100644 crates/re_query_cache/Cargo.toml create mode 100644 crates/re_query_cache/benches/flat_vec_deque.rs create mode 100644 crates/re_query_cache/src/flat_vec_deque.rs create mode 100644 crates/re_query_cache/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index e1522417ab88..283079f9fbac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4812,6 +4812,22 @@ dependencies = [ "thiserror", ] +[[package]] +name = "re_query_cache" +version = "0.12.0-alpha.3" +dependencies = [ + "ahash 0.8.6", + "criterion", + "document-features", + "itertools 0.12.0", + "mimalloc", + "re_log_types", + "re_query", + "re_types_core", + "similar-asserts", + "thiserror", +] + [[package]] name = "re_renderer" version = "0.12.0-alpha.3" diff --git a/Cargo.toml b/Cargo.toml index e133823b1a08..7fee8eb1f6a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ re_log_encoding = { path = "crates/re_log_encoding", version = "=0.12.0-alpha.3" re_log_types = { path = "crates/re_log_types", version = "=0.12.0-alpha.3", default-features = false } re_memory = { path = "crates/re_memory", version = "=0.12.0-alpha.3", default-features = false } re_query = { path = "crates/re_query", version = "=0.12.0-alpha.3", default-features = false } +re_query_cache = { path = "crates/re_query_cache", version = "=0.12.0-alpha.3", default-features = false } re_renderer = { path = "crates/re_renderer", version = "=0.12.0-alpha.3", default-features = false } re_sdk = { path = "crates/re_sdk", version = "=0.12.0-alpha.3", default-features = false } re_sdk_comms = { path = "crates/re_sdk_comms", version = "=0.12.0-alpha.3", default-features = false } diff --git a/crates/re_query_cache/Cargo.toml b/crates/re_query_cache/Cargo.toml new file mode 100644 index 000000000000..1f2365dfb683 --- /dev/null +++ b/crates/re_query_cache/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "re_query_cache" +authors.workspace = true +description = "Caching datastructures for re_query" +edition.workspace = true +homepage.workspace = true +include.workspace = true +license.workspace = true +publish = true +readme = "README.md" +repository.workspace = true +rust-version.workspace = true +version.workspace = true + +[package.metadata.docs.rs] +all-features = true + + +[features] +default = [] + +[dependencies] +# Rerun dependencies: +re_log_types.workspace = true +re_query.workspace = true +re_types_core.workspace = true + +# External dependencies: +ahash.workspace = true +document-features.workspace = true +itertools.workspace = true +thiserror.workspace = true + + +[dev-dependencies] +re_log_types = { workspace = true, features = ["testing"] } + +criterion.workspace = true +mimalloc.workspace = true +similar-asserts.workspace = true + + +[lib] +bench = false + + +[[bench]] +name = "flat_vec_deque" +harness = false diff --git a/crates/re_query_cache/benches/flat_vec_deque.rs b/crates/re_query_cache/benches/flat_vec_deque.rs new file mode 100644 index 000000000000..d12e10293c0e --- /dev/null +++ b/crates/re_query_cache/benches/flat_vec_deque.rs @@ -0,0 +1,333 @@ +use criterion::{criterion_group, criterion_main, Criterion}; + +use itertools::Itertools as _; + +use re_query_cache::FlatVecDeque; + +// --- + +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + +criterion_group!( + benches, + range, + insert, + insert_many, + insert_deque, + remove, + remove_range +); +criterion_main!(benches); + +// --- + +// `cargo test` also runs the benchmark setup code, so make sure they run quickly: +#[cfg(debug_assertions)] +mod constants { + pub const INITIAL_VALUES_PER_ENTRY: usize = 1; + pub const INITIAL_NUM_ENTRIES: usize = 1; + pub const ADDED_VALUES_PER_ENTRY: usize = 1; + pub const ADDED_NUM_ENTRIES: usize = 1; +} + +#[cfg(not(debug_assertions))] +mod constants { + pub const INITIAL_VALUES_PER_ENTRY: usize = 1000; + pub const INITIAL_NUM_ENTRIES: usize = 100; + pub const ADDED_VALUES_PER_ENTRY: usize = 1000; + pub const ADDED_NUM_ENTRIES: usize = 5; +} + +#[allow(clippy::wildcard_imports)] +use self::constants::*; + +// --- + +fn range(c: &mut Criterion) { + if std::env::var("CI").is_ok() { + return; + } + + let mut group = c.benchmark_group("flat_vec_deque"); + group.throughput(criterion::Throughput::Elements( + (ADDED_NUM_ENTRIES * ADDED_VALUES_PER_ENTRY) as _, + )); + + { + group.bench_function("range/prefilled/front", |b| { + let base = create_prefilled(); + b.iter(|| { + let v: FlatVecDeque = base.clone(); + v.range(0..ADDED_NUM_ENTRIES) + .map(ToOwned::to_owned) + .collect_vec() + }); + }); + group.bench_function("range/prefilled/middle", |b| { + let base = create_prefilled(); + b.iter(|| { + let v: FlatVecDeque = base.clone(); + v.range( + INITIAL_NUM_ENTRIES / 2 - ADDED_NUM_ENTRIES / 2 + ..INITIAL_NUM_ENTRIES / 2 + ADDED_NUM_ENTRIES / 2, + ) + .map(ToOwned::to_owned) + .collect_vec() + }); + }); + group.bench_function("range/prefilled/back", |b| { + let base = create_prefilled(); + b.iter(|| { + let v: FlatVecDeque = base.clone(); + v.range(INITIAL_NUM_ENTRIES - ADDED_NUM_ENTRIES..INITIAL_NUM_ENTRIES) + .map(ToOwned::to_owned) + .collect_vec() + }); + }); + } +} + +fn insert(c: &mut Criterion) { + if std::env::var("CI").is_ok() { + return; + } + + let added = (0..ADDED_VALUES_PER_ENTRY as i64).collect_vec(); + + let mut group = c.benchmark_group("flat_vec_deque"); + group.throughput(criterion::Throughput::Elements(added.len() as _)); + + { + group.bench_function("insert/empty", |b| { + b.iter(|| { + let mut v: FlatVecDeque = FlatVecDeque::new(); + v.insert(0, added.clone()); + v + }); + }); + } + + { + group.bench_function("insert/prefilled/front", |b| { + let base = create_prefilled(); + b.iter(|| { + let mut v: FlatVecDeque = base.clone(); + v.insert(0, added.clone()); + v + }); + }); + group.bench_function("insert/prefilled/middle", |b| { + let base = create_prefilled(); + b.iter(|| { + let mut v: FlatVecDeque = base.clone(); + v.insert(INITIAL_NUM_ENTRIES / 2, added.clone()); + v + }); + }); + group.bench_function("insert/prefilled/back", |b| { + let base = create_prefilled(); + b.iter(|| { + let mut v: FlatVecDeque = base.clone(); + v.insert(INITIAL_NUM_ENTRIES, added.clone()); + v + }); + }); + } +} + +fn insert_many(c: &mut Criterion) { + if std::env::var("CI").is_ok() { + return; + } + + let added = (0..ADDED_NUM_ENTRIES as i64) + .map(|_| (0..ADDED_VALUES_PER_ENTRY as i64).collect_vec()) + .collect_vec(); + + let mut group = c.benchmark_group("flat_vec_deque"); + group.throughput(criterion::Throughput::Elements( + (ADDED_NUM_ENTRIES * ADDED_VALUES_PER_ENTRY) as _, + )); + + { + group.bench_function("insert_many/empty", |b| { + b.iter(|| { + let mut v: FlatVecDeque = FlatVecDeque::new(); + v.insert_many(0, added.clone()); + v + }); + }); + } + + { + group.bench_function("insert_many/prefilled/front", |b| { + let base = create_prefilled(); + b.iter(|| { + let mut v: FlatVecDeque = base.clone(); + v.insert_many(0, added.clone()); + v + }); + }); + group.bench_function("insert_many/prefilled/middle", |b| { + let base = create_prefilled(); + b.iter(|| { + let mut v: FlatVecDeque = base.clone(); + v.insert_many(INITIAL_NUM_ENTRIES / 2, added.clone()); + v + }); + }); + group.bench_function("insert_many/prefilled/back", |b| { + let base = create_prefilled(); + b.iter(|| { + let mut v: FlatVecDeque = base.clone(); + v.insert_many(INITIAL_NUM_ENTRIES, added.clone()); + v + }); + }); + } +} + +fn insert_deque(c: &mut Criterion) { + if std::env::var("CI").is_ok() { + return; + } + + let mut added: FlatVecDeque = FlatVecDeque::new(); + for i in 0..ADDED_NUM_ENTRIES { + added.insert(i, (0..ADDED_VALUES_PER_ENTRY as i64).collect_vec()); + } + + let added = FlatVecDeque::from_vecs( + std::iter::repeat_with(|| (0..ADDED_VALUES_PER_ENTRY as i64).collect_vec()) + .take(ADDED_NUM_ENTRIES), + ); + + let mut group = c.benchmark_group("flat_vec_deque"); + group.throughput(criterion::Throughput::Elements( + (ADDED_NUM_ENTRIES * ADDED_VALUES_PER_ENTRY) as _, + )); + + { + group.bench_function("insert_deque/empty", |b| { + b.iter(|| { + let mut v: FlatVecDeque = FlatVecDeque::new(); + v.insert_deque(0, added.clone()); + v + }); + }); + } + + { + group.bench_function("insert_deque/prefilled/front", |b| { + let base = create_prefilled(); + b.iter(|| { + let mut v: FlatVecDeque = base.clone(); + v.insert_deque(0, added.clone()); + v + }); + }); + group.bench_function("insert_deque/prefilled/middle", |b| { + let base = create_prefilled(); + b.iter(|| { + let mut v: FlatVecDeque = base.clone(); + v.insert_deque(INITIAL_NUM_ENTRIES / 2, added.clone()); + v + }); + }); + group.bench_function("insert_deque/prefilled/back", |b| { + let base = create_prefilled(); + b.iter(|| { + let mut v: FlatVecDeque = base.clone(); + v.insert_deque(INITIAL_NUM_ENTRIES, added.clone()); + v + }); + }); + } +} + +fn remove(c: &mut Criterion) { + if std::env::var("CI").is_ok() { + return; + } + + let mut group = c.benchmark_group("flat_vec_deque"); + group.throughput(criterion::Throughput::Elements(1)); + + { + group.bench_function("remove/prefilled/front", |b| { + let base = create_prefilled(); + b.iter(|| { + let mut v: FlatVecDeque = base.clone(); + v.remove(0); + v + }); + }); + group.bench_function("remove/prefilled/middle", |b| { + let base = create_prefilled(); + b.iter(|| { + let mut v: FlatVecDeque = base.clone(); + v.remove(INITIAL_NUM_ENTRIES / 2); + v + }); + }); + group.bench_function("remove/prefilled/back", |b| { + let base = create_prefilled(); + b.iter(|| { + let mut v: FlatVecDeque = base.clone(); + v.remove(INITIAL_NUM_ENTRIES - 1); + v + }); + }); + } +} + +fn remove_range(c: &mut Criterion) { + if std::env::var("CI").is_ok() { + return; + } + + let mut group = c.benchmark_group("flat_vec_deque"); + group.throughput(criterion::Throughput::Elements( + (ADDED_NUM_ENTRIES * ADDED_VALUES_PER_ENTRY) as _, + )); + + { + group.bench_function("remove_range/prefilled/front", |b| { + let base = create_prefilled(); + b.iter(|| { + let mut v: FlatVecDeque = base.clone(); + v.remove_range(0..ADDED_NUM_ENTRIES); + v + }); + }); + group.bench_function("remove_range/prefilled/middle", |b| { + let base = create_prefilled(); + b.iter(|| { + let mut v: FlatVecDeque = base.clone(); + v.remove_range( + INITIAL_NUM_ENTRIES / 2 - ADDED_NUM_ENTRIES / 2 + ..INITIAL_NUM_ENTRIES / 2 + ADDED_NUM_ENTRIES / 2, + ); + v + }); + }); + group.bench_function("remove_range/prefilled/back", |b| { + let base = create_prefilled(); + b.iter(|| { + let mut v: FlatVecDeque = base.clone(); + v.remove_range(INITIAL_NUM_ENTRIES - ADDED_NUM_ENTRIES..INITIAL_NUM_ENTRIES); + v + }); + }); + } +} + +// --- + +fn create_prefilled() -> FlatVecDeque { + FlatVecDeque::from_vecs( + std::iter::repeat_with(|| (0..INITIAL_VALUES_PER_ENTRY as i64).collect_vec()) + .take(INITIAL_NUM_ENTRIES), + ) +} diff --git a/crates/re_query_cache/src/flat_vec_deque.rs b/crates/re_query_cache/src/flat_vec_deque.rs new file mode 100644 index 000000000000..eb8d93a19dde --- /dev/null +++ b/crates/re_query_cache/src/flat_vec_deque.rs @@ -0,0 +1,866 @@ +use std::{collections::VecDeque, ops::Range}; + +use itertools::Itertools as _; + +use re_types_core::SizeBytes; + +// --- + +/// A [`FlatVecDeque`] that can be erased into a trait object. +/// +/// Methods that don't require monomorphization over `T` are made dynamically dispatchable. +pub trait ErasedFlatVecDeque: std::any::Any { + fn as_any(&self) -> &dyn std::any::Any; + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any; + + fn into_any(self: Box) -> Box; + + /// Dynamically dispatches to [`FlatVecDeque::remove`]. + /// + /// This is prefixed with `dyn_` to avoid method dispatch ambiguities that are very hard to + /// avoid even with explicit syntax and that silently lead to infinite recursions. + fn dyn_remove(&mut self, at: usize); + + /// Dynamically dispatches to [`FlatVecDeque::remove`]. + /// + /// This is prefixed with `dyn_` to avoid method dispatch ambiguities that are very hard to + /// avoid even with explicit syntax and that silently lead to infinite recursions. + fn dyn_remove_range(&mut self, range: Range); + + /// Dynamically dispatches to [`FlatVecDeque::truncate`]. + /// + /// This is prefixed with `dyn_` to avoid method dispatch ambiguities that are very hard to + /// avoid even with explicit syntax and that silently lead to infinite recursions. + fn dyn_truncate(&mut self, at: usize); +} + +impl ErasedFlatVecDeque for FlatVecDeque { + #[inline] + fn as_any(&self) -> &dyn std::any::Any { + self + } + + #[inline] + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } + + #[inline] + fn into_any(self: Box) -> Box { + self + } + + #[inline] + fn dyn_remove(&mut self, at: usize) { + FlatVecDeque::::remove(self, at); + } + + #[inline] + fn dyn_remove_range(&mut self, range: Range) { + FlatVecDeque::::remove_range(self, range); + } + + #[inline] + fn dyn_truncate(&mut self, at: usize) { + FlatVecDeque::::truncate(self, at); + } +} + +// --- + +/// A double-ended queue implemented with a pair of growable ring buffers, where every single +/// entry is a flattened array of values. +/// +/// Logically like a `VecDeque>`, but with a less fragmented memory layout (each `Box<[T]>` +/// gets copied/inlined into the `FlatVecDeque`). +/// `FlatVecDeque` therefore optimizes for reads (cache locality, specifically) while `VecDeque>` +/// optimizes for writes. +/// +/// You can think of this as the native/deserialized version of an Arrow `ListArray`. +/// This is particularly useful when working with many small arrays of data (e.g. Rerun's +/// `TimeSeriesScalar`s). +// +// TODO(cmc): We could even use a bitmap for T=Option, which would bring this that much +// closer to a deserialized version of an Arrow array. +#[derive(Debug, Clone)] +pub struct FlatVecDeque { + /// Stores every value in the `FlatVecDeque` in a flattened `VecDeque`. + /// + /// E.g.: + /// - `FlatVecDeque[]` -> values=`[]`. + /// - `FlatVecDeque[[], [], []]` -> values=`[]`. + /// - `FlatVecDeque[[], [0], [1, 2, 3], [4, 5]]` -> values=`[0, 1, 2, 3, 4, 5]`. + values: VecDeque, + + /// Keeps track of each entry, i.e. logical slices of data. + /// + /// E.g.: + /// - `FlatVecDeque[]` -> offsets=`[]`. + /// - `FlatVecDeque[[], [], []]` -> offsets=`[0, 0, 0]`. + /// - `FlatVecDeque[[], [0], [1, 2, 3], [4, 5]]` -> offsets=`[0, 1, 4, 6]`. + offsets: VecDeque, +} + +impl SizeBytes for FlatVecDeque { + #[inline] + fn heap_size_bytes(&self) -> u64 { + // NOTE: It's all on the heap at this point. + + let values_size_bytes = if T::is_pod() { + (self.num_values() * std::mem::size_of::()) as _ + } else { + self.values + .iter() + .map(SizeBytes::total_size_bytes) + .sum::() + }; + + let offsets_size_bytes = self.num_entries() * std::mem::size_of::(); + + values_size_bytes + offsets_size_bytes as u64 + } +} + +impl Default for FlatVecDeque { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl FlatVecDeque { + #[inline] + pub const fn new() -> Self { + Self { + values: VecDeque::new(), + offsets: VecDeque::new(), + } + } + + #[inline] + pub fn from_vecs(entries: impl IntoIterator>) -> Self { + let mut this = Self::new(); + + // NOTE: Do not use any of the insertion methods, they rely on `from_vecs` in the first + // place! + let mut value_offset = 0; + for entry in entries { + value_offset += entry.len(); // increment first! + this.offsets.push_back(value_offset); + this.values.extend(entry); + } + + this + } + + /// How many entries are there in the deque? + /// + /// Keep in mind: each entry is itself an array of values. + /// Use [`Self::num_values`] to get the total number of values across all entries. + #[inline] + pub fn num_entries(&self) -> usize { + self.offsets.len() + } + + /// How many values are there in the deque? + /// + /// Keep in mind: each entry in the deque holds an array of values. + /// Use [`Self::num_entries`] to get the total number of entries, irrelevant of how many + /// values each entry holds. + #[inline] + pub fn num_values(&self) -> usize { + self.values.len() + } + + #[inline] + fn value_offset(&self, entry_index: usize) -> usize { + if entry_index == 0 { + 0 + } else { + self.offsets[entry_index - 1] + } + } + + #[inline] + fn iter_offset_ranges(&self) -> impl Iterator> + '_ { + std::iter::once(0) + .chain(self.offsets.iter().copied()) + .tuple_windows::<(_, _)>() + .map(|(start, end)| (start..end)) + } +} + +// --- + +impl FlatVecDeque { + /// Iterates over all the entries in the deque. + /// + /// This is the same as `self.range(0..self.num_entries())`. + /// + /// Keep in mind that each entry is an array of values! + #[inline] + pub fn iter(&self) -> impl Iterator { + self.range(0..self.num_entries()) + } + + /// Iterates over all the entries in the deque in the given `entry_range`. + /// + /// Keep in mind that each entry is an array of values! + #[inline] + pub fn range(&self, entry_range: Range) -> impl Iterator { + let (values_left, values_right) = self.values.as_slices(); + // NOTE: We can't slice into our offsets, we don't even know if they're contiguous in + // memory at this point -> skip() and take(). + self.iter_offset_ranges() + .skip(entry_range.start) + .take(entry_range.len()) + .map(|offsets| { + // NOTE: We do not need `make_contiguous` here because we always guarantee + // that a single entry's worth of values is fully contained in either the left or + // right buffer, but never straddling across both. + if offsets.start < values_left.len() { + &values_left[offsets] + } else { + &values_right[offsets] + } + }) + } +} + +#[test] +fn range() { + let mut v: FlatVecDeque = FlatVecDeque::new(); + + assert_eq!(0, v.num_entries()); + assert_eq!(0, v.num_values()); + + v.insert_many(0, [vec![1, 2, 3], vec![4, 5, 6, 7], vec![8, 9, 10]]); + assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9, 10]], &v); + + assert_iter_eq(&[&[1, 2, 3]], v.range(0..1)); + assert_iter_eq(&[&[4, 5, 6, 7]], v.range(1..2)); + assert_iter_eq(&[&[8, 9, 10]], v.range(2..3)); + + assert_iter_eq( + &[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9, 10]], + v.range(0..v.num_entries()), + ); + + assert_iter_eq(&[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9, 10]], v.iter()); +} + +// --- + +impl FlatVecDeque { + /// Prepends an entry comprised of `values` to the deque. + /// + /// This is the same as `self.insert(0, values)`. + /// + /// See [`Self::insert`] for more information. + #[inline] + pub fn push_front(&mut self, values: impl IntoIterator) { + self.insert(0, values); + } + + /// Appends an entry comprised of `values` to the deque. + /// + /// This is the same as `self.insert(self.num_entries(), values)`. + /// + /// See [`Self::insert`] for more information. + #[inline] + pub fn push_back(&mut self, values: impl IntoIterator) { + self.insert(self.num_entries(), values); + } + + /// Inserts a single entry at `entry_index`, comprised of the multiple elements given as `values`. + /// + /// This is O(1) if `entry_index` corresponds to either the start or the end of the deque. + /// Otherwise, this requires splitting the deque into two pieces then stitching them back together + /// at both ends of the added data. + /// + /// Panics if `entry_index` is out of bounds. + /// Panics if `values` is empty. + #[inline] + pub fn insert(&mut self, entry_index: usize, values: impl IntoIterator) { + let values: VecDeque = values.into_iter().collect(); + let num_values = values.len(); + let deque = Self { + values, + offsets: std::iter::once(num_values).collect(), + }; + self.insert_deque(entry_index, deque); + } + + /// Prepends multiple entries, each comprised of the multiple elements given in `entries`, + /// to the deque. + /// + /// This is the same as `self.insert_many(0, entries)`. + /// + /// See [`Self::insert_many`] for more information. + #[inline] + pub fn push_many_front(&mut self, entries: impl IntoIterator>) { + self.insert_many(0, entries); + } + + /// Appends multiple entries, each comprised of the multiple elements given in `entries`, + /// to the deque. + /// + /// This is the same as `self.insert_many(self.num_entries(), entries)`. + /// + /// See [`Self::insert_many`] for more information. + #[inline] + pub fn push_many_back(&mut self, entries: impl IntoIterator>) { + self.insert_many(self.num_entries(), entries); + } + + /// Inserts multiple entries, starting at `entry_index` onwards, each comprised of the multiple elements + /// given in `entries`. + /// + /// This is O(1) if `entry_index` corresponds to either the start or the end of the deque. + /// Otherwise, this requires splitting the deque into two pieces then stitching them back together + /// at both ends of the added data. + /// + /// Panics if `entry_index` is out of bounds. + /// Panics if any of the value arrays in `entries` is empty. + #[inline] + pub fn insert_many(&mut self, entry_index: usize, entries: impl IntoIterator>) { + let deque = Self::from_vecs(entries); + self.insert_deque(entry_index, deque); + } + + /// Prepends another full deque to the deque. + /// + /// This is the same as `self.insert_deque(0, rhs)`. + /// + /// See [`Self::insert_deque`] for more information. + #[inline] + pub fn push_front_deque(&mut self, rhs: FlatVecDeque) { + self.insert_deque(0, rhs); + } + + /// Appends another full deque to the deque. + /// + /// This is the same as `self.insert_deque(0, rhs)`. + /// + /// See [`Self::insert_deque`] for more information. + #[inline] + pub fn push_back_deque(&mut self, rhs: FlatVecDeque) { + self.insert_deque(self.num_entries(), rhs); + } + + /// Inserts another full deque, starting at `entry_index` and onwards. + /// + /// This is O(1) if `entry_index` corresponds to either the start or the end of the deque. + /// Otherwise, this requires splitting the deque into two pieces then stitching them back together + /// at both ends of the added data. + /// + /// Panics if `entry_index` is out of bounds. + /// Panics if any of the value arrays in `entries` is empty. + pub fn insert_deque(&mut self, entry_index: usize, mut rhs: FlatVecDeque) { + // NOTE: We're inserting _beyond_ the last element. + if entry_index == self.num_entries() { + let max_value_offset = self.offsets.back().copied().unwrap_or_default(); + self.offsets + .extend(rhs.offsets.into_iter().map(|o| o + max_value_offset)); + self.values.extend(rhs.values); + return; + } else if entry_index == 0 { + rhs.push_back_deque(std::mem::take(self)); + *self = rhs; + return; + } + + let right = self.split_off(entry_index); + self.push_back_deque(rhs); + self.push_back_deque(right); + + debug_assert!(self.iter_offset_ranges().all(|r| r.start < r.end)); + } +} + +#[test] +fn insert() { + let mut v: FlatVecDeque = FlatVecDeque::new(); + + assert_eq!(0, v.num_entries()); + assert_eq!(0, v.num_values()); + + v.insert(0, [1, 2, 3]); + assert_deque_eq(&[&[1, 2, 3]], &v); + + v.insert(0, [4, 5, 6, 7]); + assert_deque_eq(&[&[4, 5, 6, 7], &[1, 2, 3]], &v); + + v.insert(0, [8, 9]); + assert_deque_eq(&[&[8, 9], &[4, 5, 6, 7], &[1, 2, 3]], &v); + + v.insert(2, [10, 11, 12, 13]); + assert_deque_eq(&[&[8, 9], &[4, 5, 6, 7], &[10, 11, 12, 13], &[1, 2, 3]], &v); + + v.insert(v.num_entries(), [14, 15]); + assert_deque_eq( + &[ + &[8, 9], + &[4, 5, 6, 7], + &[10, 11, 12, 13], + &[1, 2, 3], + &[14, 15], + ], + &v, + ); + + v.insert(v.num_entries() - 1, [42]); + assert_deque_eq( + &[ + &[8, 9], + &[4, 5, 6, 7], + &[10, 11, 12, 13], + &[1, 2, 3], + &[42], + &[14, 15], + ], + &v, + ); +} + +#[test] +fn insert_empty() { + let mut v: FlatVecDeque = FlatVecDeque::new(); + + assert_eq!(0, v.num_entries()); + assert_eq!(0, v.num_values()); + + v.push_back([]); + v.push_back([]); + v.push_back([]); + + assert_deque_eq(&[&[], &[], &[]], &v); +} + +#[test] +fn insert_many() { + let mut v: FlatVecDeque = FlatVecDeque::new(); + + assert_eq!(0, v.num_entries()); + assert_eq!(0, v.num_values()); + + v.insert_many(0, [vec![1, 2, 3], vec![4, 5, 6, 7], vec![8, 9, 10]]); + assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9, 10]], &v); + + v.insert_many(0, [vec![20], vec![21], vec![22]]); + assert_deque_eq( + &[&[20], &[21], &[22], &[1, 2, 3], &[4, 5, 6, 7], &[8, 9, 10]], + &v, + ); + + v.insert_many(4, [vec![41, 42], vec![43]]); + assert_deque_eq( + &[ + &[20], + &[21], + &[22], + &[1, 2, 3], + &[41, 42], + &[43], + &[4, 5, 6, 7], + &[8, 9, 10], + ], + &v, + ); + + v.insert_many(v.num_entries(), [vec![100], vec![200, 300, 400]]); + assert_deque_eq( + &[ + &[20], + &[21], + &[22], + &[1, 2, 3], + &[41, 42], + &[43], + &[4, 5, 6, 7], + &[8, 9, 10], + &[100], + &[200, 300, 400], + ], + &v, + ); +} + +#[test] +fn insert_deque() { + let mut v: FlatVecDeque = FlatVecDeque::new(); + + assert_eq!(0, v.num_entries()); + assert_eq!(0, v.num_values()); + + v.insert_deque( + 0, + FlatVecDeque::from_vecs([vec![1, 2, 3], vec![4, 5, 6, 7], vec![8, 9, 10]]), + ); + assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9, 10]], &v); + + v.insert_deque(0, FlatVecDeque::from_vecs([vec![20], vec![21], vec![22]])); + assert_deque_eq( + &[&[20], &[21], &[22], &[1, 2, 3], &[4, 5, 6, 7], &[8, 9, 10]], + &v, + ); + + v.insert_deque(4, FlatVecDeque::from_vecs([vec![41, 42], vec![43]])); + assert_deque_eq( + &[ + &[20], + &[21], + &[22], + &[1, 2, 3], + &[41, 42], + &[43], + &[4, 5, 6, 7], + &[8, 9, 10], + ], + &v, + ); + + v.insert_deque( + v.num_entries(), + FlatVecDeque::from_vecs([vec![100], vec![200, 300, 400]]), + ); + assert_deque_eq( + &[ + &[20], + &[21], + &[22], + &[1, 2, 3], + &[41, 42], + &[43], + &[4, 5, 6, 7], + &[8, 9, 10], + &[100], + &[200, 300, 400], + ], + &v, + ); +} + +// --- + +impl FlatVecDeque { + /// Splits the deque into two at the given index. + /// + /// Returns a newly allocated `FlatVecDeque`. `self` contains entries `[0, entry_index)`, + /// and the returned deque contains entries `[entry_index, num_entries)`. + /// + /// Note that the capacity of `self` does not change. + /// + /// Panics if `entry_index` is out of bounds. + #[inline] + #[must_use = "use `.truncate()` if you don't need the other half"] + pub fn split_off(&mut self, entry_index: usize) -> Self { + let value_offset = self.value_offset(entry_index); + + let mut offsets = self.offsets.split_off(entry_index); + for offset in &mut offsets { + *offset -= value_offset; + } + + Self { + values: self.values.split_off(value_offset), + offsets, + } + } + + /// Shortens the deque, keeping all entries up to `entry_index` (excluded), and + /// dropping the rest. + /// + /// Panics if `entry_index` is out of bounds. + #[inline] + pub fn truncate(&mut self, entry_index: usize) { + self.values.truncate(self.value_offset(entry_index)); + self.offsets.truncate(entry_index); + } + + /// Removes the entry at `entry_index` from the deque. + /// + /// This is O(1) if `entry_index` corresponds to either the start or the end of the deque. + /// Otherwise, this requires splitting the deque into three pieces, dropping the superfluous + /// one, then stitching the two remaining pices back together. + /// + /// Panics if `entry_index` is out of bounds. + pub fn remove(&mut self, entry_index: usize) { + let (start_offset, end_offset) = ( + self.value_offset(entry_index), + self.value_offset(entry_index + 1), + ); + let offset_count = end_offset - start_offset; + + if entry_index + 1 == self.num_entries() { + self.offsets.truncate(self.num_entries() - 1); + self.values.truncate(self.values.len() - offset_count); + return; + } else if entry_index == 0 { + *self = self.split_off(entry_index + 1); + return; + } + + // NOTE: elegant, but way too slow :) + // let right = self.split_off(entry_index + 1); + // _ = self.split_off(self.num_entries() - 1); + // self.push_back_deque(right); + + _ = self.offsets.remove(entry_index); + for offset in self.offsets.range_mut(entry_index..) { + *offset -= offset_count; + } + + let right = self.values.split_off(end_offset); + self.values.truncate(self.values.len() - offset_count); + self.values.extend(right); + } + + /// Removes all entries within the given `entry_range` from the deque. + /// + /// This is O(1) if `entry_range` either starts at the beginning of the deque, or ends at + /// the end of the deque, or both. + /// Otherwise, this requires splitting the deque into three pieces, dropping the superfluous + /// one, then stitching the two remaining pieces back together. + /// + /// Panics if `entry_range` is either out of bounds or isn't monotonically increasing. + #[inline] + pub fn remove_range(&mut self, entry_range: Range) { + assert!(entry_range.start <= entry_range.end); + + if entry_range.start == entry_range.end { + return; + } + + let (start_offset, end_offset) = ( + self.value_offset(entry_range.start), + self.value_offset(entry_range.end), + ); + let offset_count = end_offset - start_offset; + + // Reminder: `entry_range.end` is exclusive. + if entry_range.end == self.num_entries() { + self.offsets + .truncate(self.num_entries() - entry_range.len()); + self.values.truncate(self.values.len() - offset_count); + return; + } else if entry_range.start == 0 { + *self = self.split_off(entry_range.end); + return; + } + + let right = self.split_off(entry_range.end); + _ = self.split_off(self.num_entries() - entry_range.len()); + self.push_back_deque(right); + } +} + +#[test] +fn truncate() { + let mut v: FlatVecDeque = FlatVecDeque::new(); + + assert_eq!(0, v.num_entries()); + assert_eq!(0, v.num_values()); + + v.insert_many(0, [vec![1, 2, 3], vec![4, 5, 6, 7], vec![8, 9, 10]]); + assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9, 10]], &v); + + { + let mut v = v.clone(); + v.truncate(0); + assert_deque_eq(&[], &v); + } + + { + let mut v = v.clone(); + v.truncate(1); + assert_deque_eq(&[&[1, 2, 3]], &v); + } + + { + let mut v = v.clone(); + v.truncate(2); + assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7]], &v); + } + + { + let mut v = v.clone(); + v.truncate(3); + assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9, 10]], &v); + } +} + +#[test] +fn split_off() { + let mut v: FlatVecDeque = FlatVecDeque::new(); + + assert_eq!(0, v.num_entries()); + assert_eq!(0, v.num_values()); + + v.insert_many(0, [vec![1, 2, 3], vec![4, 5, 6, 7], vec![8, 9, 10]]); + assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9, 10]], &v); + + { + let mut left = v.clone(); + let right = left.split_off(0); + + assert_deque_eq(&[], &left); + assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9, 10]], &right); + } + + { + let mut left = v.clone(); + let right = left.split_off(1); + + assert_deque_eq(&[&[1, 2, 3]], &left); + assert_deque_eq(&[&[4, 5, 6, 7], &[8, 9, 10]], &right); + } + + { + let mut left = v.clone(); + let right = left.split_off(2); + + assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7]], &left); + assert_deque_eq(&[&[8, 9, 10]], &right); + } + + { + let mut left = v.clone(); + let right = left.split_off(3); + + assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9, 10]], &left); + assert_deque_eq(&[], &right); + } +} + +#[test] +fn remove() { + let mut v: FlatVecDeque = FlatVecDeque::new(); + + assert_eq!(0, v.num_entries()); + assert_eq!(0, v.num_values()); + + v.insert(0, [1, 2, 3]); + assert_deque_eq(&[&[1, 2, 3]], &v); + + v.remove(0); + assert_deque_eq(&[], &v); + + v.insert(0, [1, 2, 3]); + assert_deque_eq(&[&[1, 2, 3]], &v); + + v.insert(1, [4, 5, 6, 7]); + assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7]], &v); + + v.insert(2, [8, 9]); + assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9]], &v); + + v.remove(0); + assert_deque_eq(&[&[4, 5, 6, 7], &[8, 9]], &v); + + v.insert(0, [1, 2, 3]); + assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9]], &v); + + v.remove(1); + assert_deque_eq(&[&[1, 2, 3], &[8, 9]], &v); + + v.insert(1, [4, 5, 6, 7]); + assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9]], &v); + + v.remove(2); + assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7]], &v); + + v.remove(0); + assert_deque_eq(&[&[4, 5, 6, 7]], &v); + + v.remove(0); + assert_deque_eq(&[], &v); +} + +#[test] +#[should_panic(expected = "Out of bounds access")] +fn remove_empty() { + let mut v: FlatVecDeque = FlatVecDeque::new(); + + assert_eq!(0, v.num_entries()); + assert_eq!(0, v.num_values()); + + v.remove(0); +} + +#[test] +#[should_panic(expected = "Out of bounds access")] +fn remove_oob() { + let mut v: FlatVecDeque = FlatVecDeque::new(); + + assert_eq!(0, v.num_entries()); + assert_eq!(0, v.num_values()); + + v.insert(0, [1, 2, 3]); + assert_deque_eq(&[&[1, 2, 3]], &v); + + assert_eq!(1, v.num_entries()); + assert_eq!(3, v.num_values()); + + v.remove(1); +} + +#[test] +fn remove_range() { + let mut v: FlatVecDeque = FlatVecDeque::new(); + + assert_eq!(0, v.num_entries()); + assert_eq!(0, v.num_values()); + + v.insert_many(0, [vec![1, 2, 3], vec![4, 5, 6, 7], vec![8, 9, 10]]); + assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9, 10]], &v); + + { + let mut v = v.clone(); + v.remove_range(0..1); + assert_deque_eq(&[&[4, 5, 6, 7], &[8, 9, 10]], &v); + } + + { + let mut v = v.clone(); + v.remove_range(1..2); + assert_deque_eq(&[&[1, 2, 3], &[8, 9, 10]], &v); + } + + { + let mut v = v.clone(); + v.remove_range(2..3); + assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7]], &v); + } + + { + let mut v = v.clone(); + v.remove_range(0..2); + assert_deque_eq(&[&[8, 9, 10]], &v); + } + + { + let mut v = v.clone(); + v.remove_range(1..3); + assert_deque_eq(&[&[1, 2, 3]], &v); + } + + { + let mut v = v.clone(); + v.remove_range(0..3); + assert_deque_eq(&[], &v); + } +} + +// --- + +#[cfg(test)] +fn assert_deque_eq(expected: &[&'_ [i64]], got: &FlatVecDeque) { + similar_asserts::assert_eq!(expected, got.iter().collect_vec()); +} + +#[cfg(test)] +fn assert_iter_eq<'a>(expected: &[&'_ [i64]], got: impl Iterator) { + similar_asserts::assert_eq!(expected, got.collect_vec()); +} diff --git a/crates/re_query_cache/src/lib.rs b/crates/re_query_cache/src/lib.rs new file mode 100644 index 000000000000..2e3da5163e0e --- /dev/null +++ b/crates/re_query_cache/src/lib.rs @@ -0,0 +1,9 @@ +//! Caching datastructures for `re_query`. + +mod flat_vec_deque; + +pub use self::flat_vec_deque::{ErasedFlatVecDeque, FlatVecDeque}; + +pub mod external { + pub use re_query; +}