Skip to content

Commit

Permalink
feat: implement keys and values on StableBTreeMap (#241)
Browse files Browse the repository at this point in the history
This adds `keys` and `values` to `StableBTreeMap` bringing it more in
line with the std `BTreeMap`.

It also adds `keys_range` and `values_range` which aren't exposed by the
std `BTreeMap`, with the std map these aren't really needed because the
keys and values are returned by reference, so it is still efficient to
use `range` and then `map` to get the keys or the values.
But with the `StableBTreeMap` using the same approach would result in
reading and deserializing the keys and values, only to throw one set of
them away.
  • Loading branch information
hpeebles authored Nov 13, 2024
1 parent 376db8d commit 1f9b516
Show file tree
Hide file tree
Showing 5 changed files with 431 additions and 44 deletions.
76 changes: 61 additions & 15 deletions benchmarks/src/btreemap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,22 +222,62 @@ pub fn btreemap_insert_10mib_values() -> BenchResult {

#[bench(raw)]
pub fn btreemap_iter_small_values() -> BenchResult {
iter_helper(10_000, 0)
iter_helper(10_000, 0, IterType::Iter)
}

#[bench(raw)]
pub fn btreemap_iter_rev_small_values() -> BenchResult {
iter_rev_helper(10_000, 0)
iter_helper(10_000, 0, IterType::IterRev)
}

#[bench(raw)]
pub fn btreemap_iter_10mib_values() -> BenchResult {
iter_helper(200, 10 * 1024)
iter_helper(200, 10 * 1024, IterType::Iter)
}

#[bench(raw)]
pub fn btreemap_iter_rev_10mib_values() -> BenchResult {
iter_rev_helper(200, 10 * 1024)
iter_helper(200, 10 * 1024, IterType::IterRev)
}

#[bench(raw)]
pub fn btreemap_keys_small_values() -> BenchResult {
iter_helper(10_000, 0, IterType::Keys)
}

#[bench(raw)]
pub fn btreemap_keys_rev_small_values() -> BenchResult {
iter_helper(10_000, 0, IterType::KeysRev)
}

#[bench(raw)]
pub fn btreemap_keys_10mib_values() -> BenchResult {
iter_helper(200, 10 * 1024, IterType::Keys)
}

#[bench(raw)]
pub fn btreemap_keys_rev_10mib_values() -> BenchResult {
iter_helper(200, 10 * 1024, IterType::KeysRev)
}

#[bench(raw)]
pub fn btreemap_values_small_values() -> BenchResult {
iter_helper(10_000, 0, IterType::Values)
}

#[bench(raw)]
pub fn btreemap_values_rev_small_values() -> BenchResult {
iter_helper(10_000, 0, IterType::ValuesRev)
}

#[bench(raw)]
pub fn btreemap_values_10mib_values() -> BenchResult {
iter_helper(200, 10 * 1024, IterType::Values)
}

#[bench(raw)]
pub fn btreemap_values_rev_10mib_values() -> BenchResult {
iter_helper(200, 10 * 1024, IterType::ValuesRev)
}

#[bench(raw)]
Expand Down Expand Up @@ -538,23 +578,20 @@ fn insert_helper<K: Clone + Ord + Storable + Random, V: Storable + Random>(
}

// Profiles iterating over a btreemap.
fn iter_helper(size: u32, value_size: u32) -> BenchResult {
fn iter_helper(size: u32, value_size: u32, iter_type: IterType) -> BenchResult {
let mut btree = BTreeMap::new(DefaultMemoryImpl::default());
for i in 0..size {
btree.insert(i, vec![0u8; value_size as usize]);
}

bench_fn(|| for _ in btree.iter() {})
}

// Profiles iterating in reverse over a btreemap.
fn iter_rev_helper(size: u32, value_size: u32) -> BenchResult {
let mut btree = BTreeMap::new(DefaultMemoryImpl::default());
for i in 0..size {
btree.insert(i, vec![0u8; value_size as usize]);
match iter_type {
IterType::Iter => bench_fn(|| for _ in btree.iter() {}),
IterType::IterRev => bench_fn(|| for _ in btree.iter().rev() {}),
IterType::Keys => bench_fn(|| for _ in btree.keys() {}),
IterType::KeysRev => bench_fn(|| for _ in btree.keys().rev() {}),
IterType::Values => bench_fn(|| for _ in btree.values() {}),
IterType::ValuesRev => bench_fn(|| for _ in btree.values().rev() {}),
}

bench_fn(|| for _ in btree.iter().rev() {})
}

// Profiles getting a large number of random blobs from a btreemap.
Expand Down Expand Up @@ -630,3 +667,12 @@ fn remove_helper<K: Clone + Ord + Storable + Random, V: Storable + Random>(
}
})
}

enum IterType {
Iter,
IterRev,
Keys,
KeysRev,
Values,
ValuesRev,
}
56 changes: 52 additions & 4 deletions canbench_results.yml
Original file line number Diff line number Diff line change
Expand Up @@ -385,13 +385,13 @@ benches:
scopes: {}
btreemap_iter_rev_10mib_values:
total:
instructions: 25585550
instructions: 25584039
heap_increase: 0
stable_memory_increase: 0
scopes: {}
btreemap_iter_rev_small_values:
total:
instructions: 23878236
instructions: 23800315
heap_increase: 0
stable_memory_increase: 0
scopes: {}
Expand All @@ -401,6 +401,30 @@ benches:
heap_increase: 0
stable_memory_increase: 0
scopes: {}
btreemap_keys_10mib_values:
total:
instructions: 534290
heap_increase: 0
stable_memory_increase: 0
scopes: {}
btreemap_keys_rev_10mib_values:
total:
instructions: 595467
heap_increase: 0
stable_memory_increase: 0
scopes: {}
btreemap_keys_rev_small_values:
total:
instructions: 14369449
heap_increase: 0
stable_memory_increase: 0
scopes: {}
btreemap_keys_small_values:
total:
instructions: 11184261
heap_increase: 0
stable_memory_increase: 0
scopes: {}
btreemap_remove_blob_128_1024:
total:
instructions: 1916049094
Expand Down Expand Up @@ -533,6 +557,30 @@ benches:
heap_increase: 0
stable_memory_increase: 0
scopes: {}
btreemap_values_10mib_values:
total:
instructions: 17277830
heap_increase: 0
stable_memory_increase: 0
scopes: {}
btreemap_values_rev_10mib_values:
total:
instructions: 17276742
heap_increase: 0
stable_memory_increase: 0
scopes: {}
btreemap_values_rev_small_values:
total:
instructions: 22619653
heap_increase: 0
stable_memory_increase: 0
scopes: {}
btreemap_values_small_values:
total:
instructions: 22560352
heap_increase: 0
stable_memory_increase: 0
scopes: {}
memory_manager_baseline:
total:
instructions: 1176577052
Expand All @@ -541,13 +589,13 @@ benches:
scopes: {}
memory_manager_grow:
total:
instructions: 350727867
instructions: 351687872
heap_increase: 2
stable_memory_increase: 32000
scopes: {}
memory_manager_overhead:
total:
instructions: 1182141676
instructions: 1182143127
heap_increase: 0
stable_memory_increase: 8320
scopes: {}
Expand Down
56 changes: 43 additions & 13 deletions src/btreemap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
mod allocator;
mod iter;
mod node;
use crate::btreemap::iter::{IterInternal, KeysIter, ValuesIter};
use crate::{
storable::Bound as StorableBound,
types::{Address, NULL},
Expand Down Expand Up @@ -1006,33 +1007,62 @@ where

/// Returns an iterator over the entries of the map, sorted by key.
pub fn iter(&self) -> Iter<K, V, M> {
Iter::new(self)
self.iter_internal().into()
}

/// Returns an iterator over the entries in the map where keys
/// belong to the specified range.
pub fn range(&self, key_range: impl RangeBounds<K>) -> Iter<K, V, M> {
self.range_internal(key_range).into()
}

/// Returns an iterator pointing to the first element below the given bound.
/// Returns an empty iterator if there are no keys below the given bound.
pub fn iter_upper_bound(&self, bound: &K) -> Iter<K, V, M> {
if let Some((start_key, _)) = self.range(..bound).next_back() {
IterInternal::new_in_range(self, (Bound::Included(start_key), Bound::Unbounded)).into()
} else {
IterInternal::null(self).into()
}
}

/// Returns an iterator over the keys of the map.
pub fn keys(&self) -> KeysIter<K, V, M> {
self.iter_internal().into()
}

/// Returns an iterator over the keys of the map which belong to the specified range.
pub fn keys_range(&self, key_range: impl RangeBounds<K>) -> KeysIter<K, V, M> {
self.range_internal(key_range).into()
}

/// Returns an iterator over the values of the map, sorted by key.
pub fn values(&self) -> ValuesIter<K, V, M> {
self.iter_internal().into()
}

/// Returns an iterator over the values of the map where keys
/// belong to the specified range.
pub fn values_range(&self, key_range: impl RangeBounds<K>) -> ValuesIter<K, V, M> {
self.range_internal(key_range).into()
}

fn iter_internal(&self) -> IterInternal<K, V, M> {
IterInternal::new(self)
}

fn range_internal(&self, key_range: impl RangeBounds<K>) -> IterInternal<K, V, M> {
if self.root_addr == NULL {
// Map is empty.
return Iter::null(self);
return IterInternal::null(self);
}

let range = (
key_range.start_bound().cloned(),
key_range.end_bound().cloned(),
);

Iter::new_in_range(self, range)
}

/// Returns an iterator pointing to the first element below the given bound.
/// Returns an empty iterator if there are no keys below the given bound.
pub fn iter_upper_bound(&self, bound: &K) -> Iter<K, V, M> {
if let Some((start_key, _)) = self.range(..bound).next_back() {
Iter::new_in_range(self, (Bound::Included(start_key), Bound::Unbounded))
} else {
Iter::null(self)
}
IterInternal::new_in_range(self, range)
}

// Merges one node (`source`) into another (`into`), along with a median entry.
Expand Down
Loading

0 comments on commit 1f9b516

Please # to comment.