-
Notifications
You must be signed in to change notification settings - Fork 373
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
GC improvements 2:
VecDeque
extensions & benchmarks (#4396)
What the title says: just introducing new tools that will be used by all the ringbuffer looking things in the revamped datastore and upcoming query cache. --- Part of the GC improvements series: - #4394 - #4395 - #4396 - #4397 - #4398 - #4399 - #4400 - #4401
- Loading branch information
Showing
5 changed files
with
307 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
//! Simple benchmark suite to keep track of how the different removal methods for [`VecDeque`] | ||
//! behave in practice. | ||
#[global_allocator] | ||
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; | ||
|
||
use std::collections::VecDeque; | ||
|
||
use criterion::{criterion_group, criterion_main, Criterion}; | ||
|
||
use re_log_types::VecDequeRemovalExt as _; | ||
|
||
// --- | ||
|
||
criterion_group!(benches, remove, swap_remove, swap_remove_front); | ||
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_NUM_ENTRIES: usize = 1; | ||
} | ||
|
||
#[cfg(not(debug_assertions))] | ||
mod constants { | ||
pub const INITIAL_NUM_ENTRIES: usize = 20_000; | ||
} | ||
|
||
#[allow(clippy::wildcard_imports)] | ||
use self::constants::*; | ||
|
||
// --- | ||
|
||
fn remove(c: &mut Criterion) { | ||
{ | ||
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: VecDeque<i64> = base.clone(); | ||
v.remove(0); | ||
v | ||
}); | ||
}); | ||
group.bench_function("remove/prefilled/middle", |b| { | ||
let base = create_prefilled(); | ||
b.iter(|| { | ||
let mut v: VecDeque<i64> = 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: VecDeque<i64> = base.clone(); | ||
v.remove(INITIAL_NUM_ENTRIES - 1); | ||
v | ||
}); | ||
}); | ||
} | ||
} | ||
|
||
fn swap_remove(c: &mut Criterion) { | ||
{ | ||
let mut group = c.benchmark_group("flat_vec_deque"); | ||
group.throughput(criterion::Throughput::Elements(1)); | ||
group.bench_function("swap_remove/prefilled/front", |b| { | ||
let base = create_prefilled(); | ||
b.iter(|| { | ||
let mut v: VecDeque<i64> = base.clone(); | ||
v.swap_remove(0); | ||
v | ||
}); | ||
}); | ||
group.bench_function("swap_remove/prefilled/middle", |b| { | ||
let base = create_prefilled(); | ||
b.iter(|| { | ||
let mut v: VecDeque<i64> = base.clone(); | ||
v.swap_remove(INITIAL_NUM_ENTRIES / 2); | ||
v | ||
}); | ||
}); | ||
group.bench_function("swap_remove/prefilled/back", |b| { | ||
let base = create_prefilled(); | ||
b.iter(|| { | ||
let mut v: VecDeque<i64> = base.clone(); | ||
v.swap_remove(INITIAL_NUM_ENTRIES - 1); | ||
v | ||
}); | ||
}); | ||
} | ||
} | ||
|
||
fn swap_remove_front(c: &mut Criterion) { | ||
{ | ||
let mut group = c.benchmark_group("flat_vec_deque"); | ||
group.throughput(criterion::Throughput::Elements(1)); | ||
group.bench_function("swap_remove_front/prefilled/front", |b| { | ||
let base = create_prefilled(); | ||
b.iter(|| { | ||
let mut v: VecDeque<i64> = base.clone(); | ||
v.swap_remove_front(0); | ||
v | ||
}); | ||
}); | ||
group.bench_function("swap_remove_front/prefilled/middle", |b| { | ||
let base = create_prefilled(); | ||
b.iter(|| { | ||
let mut v: VecDeque<i64> = base.clone(); | ||
v.swap_remove_front(INITIAL_NUM_ENTRIES / 2); | ||
v | ||
}); | ||
}); | ||
group.bench_function("swap_remove_front/prefilled/back", |b| { | ||
let base = create_prefilled(); | ||
b.iter(|| { | ||
let mut v: VecDeque<i64> = base.clone(); | ||
v.swap_remove_front(INITIAL_NUM_ENTRIES - 1); | ||
v | ||
}); | ||
}); | ||
} | ||
} | ||
|
||
// --- | ||
|
||
fn create_prefilled() -> VecDeque<i64> { | ||
let mut base: VecDeque<i64> = VecDeque::new(); | ||
base.extend(0..INITIAL_NUM_ENTRIES as i64); | ||
base | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
use std::collections::VecDeque; | ||
|
||
// --- | ||
|
||
/// Extends [`VecDeque`] with extra sorting routines. | ||
pub trait VecDequeSortingExt<T> { | ||
/// Sorts `self`. | ||
/// | ||
/// Makes sure to render `self` contiguous first, if needed. | ||
fn sort(&mut self); | ||
|
||
/// Check whether `self` is sorted. | ||
/// | ||
/// `self` doesn't need to be contiguous. | ||
fn is_sorted(&self) -> bool; | ||
} | ||
|
||
impl<T: Clone + PartialOrd + Ord> VecDequeSortingExt<T> for VecDeque<T> { | ||
#[inline] | ||
fn sort(&mut self) { | ||
self.make_contiguous(); | ||
let (values, &mut []) = self.as_mut_slices() else { | ||
unreachable!(); | ||
}; | ||
values.sort(); | ||
} | ||
|
||
#[inline] | ||
fn is_sorted(&self) -> bool { | ||
if self.is_empty() { | ||
return true; | ||
} | ||
|
||
let (left, right) = self.as_slices(); | ||
|
||
let left_before_right = || { | ||
if let (Some(left_last), Some(right_first)) = (left.last(), right.first()) { | ||
left_last <= right_first | ||
} else { | ||
true | ||
} | ||
}; | ||
let left_is_sorted = || !left.windows(2).any(|values| values[0] > values[1]); | ||
let right_is_sorted = || !right.windows(2).any(|values| values[0] > values[1]); | ||
|
||
left_before_right() && left_is_sorted() && right_is_sorted() | ||
} | ||
} | ||
|
||
#[test] | ||
fn is_sorted() { | ||
let mut v: VecDeque<i64> = vec![].into(); | ||
|
||
assert!(v.is_sorted()); | ||
|
||
v.extend([1, 2, 3]); | ||
assert!(v.is_sorted()); | ||
|
||
v.push_front(4); | ||
assert!(!v.is_sorted()); | ||
|
||
v.rotate_left(1); | ||
assert!(v.is_sorted()); | ||
|
||
v.extend([7, 6, 5]); | ||
assert!(!v.is_sorted()); | ||
|
||
v.sort(); | ||
assert!(v.is_sorted()); | ||
} | ||
|
||
// --- | ||
|
||
/// Extends [`VecDeque`] with extra removal routines. | ||
pub trait VecDequeRemovalExt<T> { | ||
/// Removes an element from anywhere in the deque and returns it, replacing it with | ||
/// whichever end element that this is closer to the removal point. | ||
/// | ||
/// If `index` points to the front or back of the queue, the removal is guaranteed to preserve | ||
/// ordering; otherwise it doesn't. | ||
/// In either case, this is *O*(1). | ||
/// | ||
/// Returns `None` if `index` is out of bounds. | ||
/// | ||
/// Element at index 0 is the front of the queue. | ||
fn swap_remove(&mut self, index: usize) -> Option<T>; | ||
|
||
/// Splits the deque into two at the given index. | ||
/// | ||
/// Returns a newly allocated `VecDeque`. `self` contains elements `[0, at)`, | ||
/// and the returned deque contains elements `[at, len)`. | ||
/// | ||
/// If `at` is equal or greater than the length, the returned `VecDeque` is empty. | ||
/// | ||
/// Note that the capacity of `self` does not change. | ||
/// | ||
/// Element at index 0 is the front of the queue. | ||
fn split_off_or_default(&mut self, at: usize) -> Self; | ||
} | ||
|
||
impl<T: Clone> VecDequeRemovalExt<T> for VecDeque<T> { | ||
#[inline] | ||
fn swap_remove(&mut self, index: usize) -> Option<T> { | ||
if self.is_empty() { | ||
return None; | ||
} | ||
|
||
if index == 0 { | ||
let v = self.get(0).cloned(); | ||
self.rotate_left(1); | ||
self.truncate(self.len() - 1); | ||
v | ||
} else if index + 1 == self.len() { | ||
let v = self.get(index).cloned(); | ||
self.truncate(self.len() - 1); | ||
v | ||
} else if index < self.len() / 2 { | ||
self.swap_remove_front(index) | ||
} else { | ||
self.swap_remove_back(index) | ||
} | ||
} | ||
|
||
#[inline] | ||
fn split_off_or_default(&mut self, at: usize) -> Self { | ||
if at >= self.len() { | ||
return Default::default(); | ||
} | ||
self.split_off(at) | ||
} | ||
} | ||
|
||
#[test] | ||
fn swap_remove() { | ||
let mut v: VecDeque<i64> = vec![].into(); | ||
|
||
assert!(v.swap_remove(0).is_none()); | ||
assert!(v.is_sorted()); | ||
|
||
v.push_front(1); | ||
assert!(v.is_sorted()); | ||
|
||
assert!(v.swap_remove(1).is_none()); | ||
assert_eq!(Some(1), v.swap_remove(0)); | ||
assert!(v.is_sorted()); | ||
|
||
v.extend([4, 5, 6, 7]); | ||
assert!(v.is_sorted()); | ||
|
||
assert_eq!(Some(4), v.swap_remove(0)); | ||
assert!(v.is_sorted()); | ||
|
||
assert_eq!(Some(7), v.swap_remove(2)); | ||
assert!(v.is_sorted()); | ||
|
||
assert_eq!(Some(6), v.swap_remove(1)); | ||
assert!(v.is_sorted()); | ||
|
||
assert_eq!(Some(5), v.swap_remove(0)); | ||
assert!(v.is_sorted()); | ||
} |