-
Notifications
You must be signed in to change notification settings - Fork 163
Flat containers #498
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Flat containers #498
Changes from all commits
d486797
7a8ee9e
54482dd
c8975dc
aab4940
06eda23
5be314f
4c0f0ed
9be3ad4
b94b56c
c4bbdee
e1c705a
82b6f4f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,86 @@ | ||||||
use std::marker::PhantomData; | ||||||
|
||||||
use crate::flatbuffers::containers::sorted_index::SortedIndex; | ||||||
use flatbuffers::{Follow, Vector}; | ||||||
|
||||||
/// A map-like container that uses flatbuffer references. | ||||||
/// Provides O(log n) lookup time using binary search on the sorted index. | ||||||
/// I is a key type, Keys is specific container of keys, &[I] for fast indexing (u32, u64) | ||||||
/// and flatbuffers::Vector<I> if there is no conversion from Vector (str) to slice. | ||||||
pub(crate) struct FlatMultiMapView<'a, I: Ord, V, Keys> | ||||||
where | ||||||
Keys: SortedIndex<I>, | ||||||
V: Follow<'a>, | ||||||
{ | ||||||
keys: Keys, | ||||||
values: Vector<'a, V>, | ||||||
_phantom: PhantomData<I>, | ||||||
} | ||||||
|
||||||
impl<'a, I: Ord + Copy, V, Keys> FlatMultiMapView<'a, I, V, Keys> | ||||||
where | ||||||
Keys: SortedIndex<I> + Clone, | ||||||
V: Follow<'a>, | ||||||
{ | ||||||
pub fn new(keys: Keys, values: Vector<'a, V>) -> Self { | ||||||
debug_assert!(keys.len() == values.len()); | ||||||
|
||||||
Self { | ||||||
keys, | ||||||
values, | ||||||
_phantom: PhantomData, | ||||||
} | ||||||
} | ||||||
|
||||||
pub fn get(&self, key: I) -> Option<FlatMultiMapViewIterator<'a, I, V, Keys>> { | ||||||
let index = self.keys.partition_point(|x| *x < key); | ||||||
if index < self.keys.len() && self.keys.get(index) == key { | ||||||
Some(FlatMultiMapViewIterator { | ||||||
index, | ||||||
key, | ||||||
keys: self.keys.clone(), // Cloning is 3-4% faster than & in benchmarks | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nitpick] The comment claims cloning is faster than references, but this seems counterintuitive and may be a premature optimization. Consider providing more context about why cloning would be faster or benchmark conditions that led to this conclusion.
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||
values: self.values, | ||||||
}) | ||||||
} else { | ||||||
None | ||||||
} | ||||||
} | ||||||
|
||||||
#[cfg(test)] | ||||||
pub fn total_size(&self) -> usize { | ||||||
self.keys.len() | ||||||
} | ||||||
} | ||||||
|
||||||
pub(crate) struct FlatMultiMapViewIterator<'a, I: Ord + Copy, V, Keys> | ||||||
where | ||||||
Keys: SortedIndex<I>, | ||||||
V: Follow<'a>, | ||||||
{ | ||||||
index: usize, | ||||||
key: I, | ||||||
keys: Keys, | ||||||
values: Vector<'a, V>, | ||||||
} | ||||||
|
||||||
impl<'a, I, V, Keys> Iterator for FlatMultiMapViewIterator<'a, I, V, Keys> | ||||||
where | ||||||
I: Ord + Copy, | ||||||
V: Follow<'a>, | ||||||
Keys: SortedIndex<I>, | ||||||
{ | ||||||
type Item = (usize, <V as Follow<'a>>::Inner); | ||||||
|
||||||
fn next(&mut self) -> Option<Self::Item> { | ||||||
if self.index < self.keys.len() && self.keys.get(self.index) == self.key { | ||||||
self.index += 1; | ||||||
Some((self.index - 1, self.values.get(self.index - 1))) | ||||||
} else { | ||||||
None | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
#[cfg(test)] | ||||||
#[path = "../../../tests/unit/flatbuffers/containers/flat_multimap.rs"] | ||||||
mod unit_tests; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
#![allow(dead_code)] | ||
|
||
use std::marker::PhantomData; | ||
|
||
use crate::flatbuffers::containers::sorted_index::SortedIndex; | ||
|
||
/// A set-like container that uses flatbuffer references. | ||
/// Provides O(log n) lookup time using binary search on the sorted data. | ||
/// I is a key type, Keys is specific container of keys, &[I] for fast indexing (u32, u64) | ||
/// and flatbuffers::Vector<I> if there is no conversion from Vector (str) to slice. | ||
pub(crate) struct FlatSetView<I, Keys> | ||
where | ||
Keys: SortedIndex<I>, | ||
{ | ||
keys: Keys, | ||
_phantom: PhantomData<I>, | ||
} | ||
|
||
impl<I, Keys> FlatSetView<I, Keys> | ||
where | ||
I: Ord, | ||
Keys: SortedIndex<I>, | ||
{ | ||
pub fn new(keys: Keys) -> Self { | ||
Self { | ||
keys, | ||
_phantom: PhantomData, | ||
} | ||
} | ||
|
||
pub fn contains(&self, key: I) -> bool { | ||
let index = self.keys.partition_point(|x| *x < key); | ||
index < self.keys.len() && self.keys.get(index) == key | ||
} | ||
|
||
#[inline(always)] | ||
pub fn len(&self) -> usize { | ||
self.keys.len() | ||
} | ||
|
||
#[inline(always)] | ||
pub fn is_empty(&self) -> bool { | ||
self.len() == 0 | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
#[path = "../../../tests/unit/flatbuffers/containers/flat_set.rs"] | ||
mod unit_tests; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
pub(crate) mod flat_multimap; | ||
pub(crate) mod flat_set; | ||
pub(crate) mod sorted_index; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
use flatbuffers::{Follow, Vector}; | ||
|
||
// Represents sorted sequence to perform the binary search. | ||
pub(crate) trait SortedIndex<I> { | ||
fn len(&self) -> usize; | ||
fn get(&self, index: usize) -> I; | ||
fn partition_point<F>(&self, predicate: F) -> usize | ||
where | ||
F: FnMut(&I) -> bool; | ||
} | ||
|
||
// Implementation for slices. Prefer using this with fb_vector_to_slice | ||
// if possible, because it faster than getting values with flatbuffer's | ||
// get method. | ||
impl<I: Ord + Copy> SortedIndex<I> for &[I] { | ||
#[inline(always)] | ||
fn len(&self) -> usize { | ||
<[I]>::len(self) | ||
} | ||
|
||
#[inline(always)] | ||
fn get(&self, index: usize) -> I { | ||
self[index] | ||
} | ||
|
||
#[inline(always)] | ||
fn partition_point<F>(&self, predicate: F) -> usize | ||
where | ||
F: FnMut(&I) -> bool, | ||
{ | ||
debug_assert!(self.is_sorted()); | ||
<[I]>::partition_point(self, predicate) | ||
} | ||
} | ||
|
||
// General implementation for flatbuffers::Vector, it uses get to | ||
// obtain values. | ||
impl<'a, T: Follow<'a>> SortedIndex<T::Inner> for Vector<'a, T> | ||
where | ||
T::Inner: Ord, | ||
{ | ||
#[inline(always)] | ||
fn len(&self) -> usize { | ||
Vector::len(self) | ||
} | ||
|
||
#[inline(always)] | ||
fn get(&self, index: usize) -> T::Inner { | ||
Vector::get(self, index) | ||
} | ||
|
||
fn partition_point<F>(&self, mut predicate: F) -> usize | ||
where | ||
F: FnMut(&T::Inner) -> bool, | ||
{ | ||
debug_assert!(self.iter().is_sorted()); | ||
|
||
let mut left = 0; | ||
let mut right = self.len(); | ||
|
||
while left < right { | ||
atuchin-m marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let mid = left + (right - left) / 2; | ||
let value = self.get(mid); | ||
if predicate(&value) { | ||
left = mid + 1; | ||
} else { | ||
right = mid; | ||
} | ||
} | ||
|
||
left | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
pub(crate) mod containers; | ||
pub(crate) mod unsafe_tools; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we're accumulating several of these flatbuffer utility structs, I'm wondering if it makes sense to create and publish a new separate crate just for those? I imagine others in the Rust community could find them useful too.
No need to do so here, but something to consider.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That could be an option once we finally stabilize the interface and merge it to master.