Skip to content

perf: lazy-loaded keys for BTreeMap node #281

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

Draft
wants to merge 54 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
e8ac97c
refactor lazy-loaded Value
maksymar Apr 9, 2025
d9ed59d
LazyBlob
maksymar Apr 9, 2025
e578349
cleanup
maksymar Apr 9, 2025
dab3f33
LazyObject<T>
maksymar Apr 9, 2025
cddb20d
.
maksymar Apr 9, 2025
180562f
.
maksymar Apr 10, 2025
4c80160
.
maksymar Apr 10, 2025
0f37610
.
maksymar Apr 10, 2025
82dd969
.
maksymar Apr 10, 2025
0747e62
.
maksymar Apr 10, 2025
a90e6fb
.
maksymar Apr 10, 2025
ab74398
.
maksymar Apr 10, 2025
ff58a12
.
maksymar Apr 10, 2025
3f19c29
.
maksymar Apr 10, 2025
6b44582
.
maksymar Apr 10, 2025
18a2c8f
.
maksymar Apr 10, 2025
4b9b6d2
.
maksymar Apr 10, 2025
177dd68
.
maksymar Apr 10, 2025
6148f5b
.
maksymar Apr 10, 2025
de4f0f5
.
maksymar Apr 10, 2025
51bb026
.
maksymar Apr 10, 2025
9f82cca
.
maksymar Apr 10, 2025
3b576af
proptests
maksymar Apr 10, 2025
5994df9
.
maksymar Apr 10, 2025
f50ee36
.
maksymar Apr 10, 2025
3002f45
.
maksymar Apr 10, 2025
d2f396a
.
maksymar Apr 10, 2025
bfb55e4
.
maksymar Apr 10, 2025
98ecd3e
.
maksymar Apr 10, 2025
70e3648
.
maksymar Apr 10, 2025
557a872
.
maksymar Apr 10, 2025
8538cc3
fix btreemap len test
maksymar Apr 10, 2025
180331f
cleanup
maksymar Apr 10, 2025
1b63f74
debugging
maksymar Apr 11, 2025
ef6bd66
.
maksymar Apr 15, 2025
11824eb
.
maksymar Apr 15, 2025
db2848f
.
maksymar Apr 15, 2025
febd822
fix write/read key
maksymar Apr 15, 2025
ec5e069
.
maksymar Apr 15, 2025
82f7aac
.
maksymar Apr 15, 2025
31c5dfd
.
maksymar Apr 15, 2025
71ca553
partial fix for len test: v1 tbd, v1->v2 ok, v2 ok
maksymar Apr 15, 2025
cc931f7
.
maksymar Apr 15, 2025
f57e8ad
len test fix both v1 and v2
maksymar Apr 15, 2025
c24fb78
btreemap::test::len cleanup
maksymar Apr 15, 2025
ee60a5b
pop_first_len cleanup
maksymar Apr 15, 2025
b25d01a
add debug code
maksymar Apr 15, 2025
80565b3
merge main
maksymar Apr 18, 2025
8699bf6
.
maksymar Apr 18, 2025
1992074
turn off debug output
maksymar Apr 18, 2025
aa77530
.
maksymar Apr 18, 2025
a99eda5
Merge branch 'maksym/base' into maksym/lazy-keys
maksymar Apr 18, 2025
53fdfc5
add LOAD_SIZE_THRESHOLD
maksymar Apr 18, 2025
ad71aac
disable size threshold
maksymar Apr 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
525 changes: 501 additions & 24 deletions benchmarks/src/btreemap.rs

Large diffs are not rendered by default.

100 changes: 56 additions & 44 deletions src/btreemap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ where
let mut root = self.load_node(self.root_addr);

// Check if the key already exists in the root.
if let Ok(idx) = root.search(&key) {
if let Ok(idx) = root.search(&key, self.memory()) {
// The key exists. Overwrite it and return the previous value.
let (_, previous_value) = root.swap_entry(idx, (key, value), self.memory());
self.save_node(&mut root);
Expand Down Expand Up @@ -409,7 +409,7 @@ where
assert!(!node.is_full());

// Look for the key in the node.
match node.search(&key) {
match node.search(&key, self.memory()) {
Ok(idx) => {
// The key is already in the node.
// Overwrite it and return the previous value.
Expand Down Expand Up @@ -442,7 +442,7 @@ where

if child.is_full() {
// Check if the key already exists in the child.
if let Ok(idx) = child.search(&key) {
if let Ok(idx) = child.search(&key, self.memory()) {
// The key exists. Overwrite it and return the previous value.
let (_, previous_value) =
child.swap_entry(idx, (key, value), self.memory());
Expand All @@ -455,7 +455,7 @@ where

// The children have now changed. Search again for
// the child where we need to store the entry in.
let idx = node.search(&key).unwrap_or_else(|idx| idx);
let idx = node.search(&key, self.memory()).unwrap_or_else(|idx| idx);
child = self.load_node(node.child(idx));
}

Expand All @@ -469,7 +469,7 @@ where
}
}

/// Takes as input a nonfull internal `node` and index to its full child, then
/// Takes as input a non-full internal `node` and index to its full child, then
/// splits this child into two, adding an additional child to `node`.
///
/// Example:
Expand Down Expand Up @@ -535,7 +535,7 @@ where
{
let node = self.load_node(node_addr);
// Look for the key in the current node.
match node.search(key) {
match node.search(key, self.memory()) {
Ok(idx) => Some(f(node, idx)), // Key found: apply `f`.
Err(idx) => match node.node_type() {
NodeType::Leaf => None, // At a leaf: key not present.
Expand Down Expand Up @@ -652,7 +652,7 @@ where

match node.node_type() {
NodeType::Leaf => {
match node.search(key) {
match node.search(key, self.memory()) {
Ok(idx) => {
// Case 1: The node is a leaf node and the key exists in it.
// This is the simplest case. The key is removed from the leaf.
Expand All @@ -679,7 +679,7 @@ where
}
}
NodeType::Internal => {
match node.search(key) {
match node.search(key, self.memory()) {
Ok(idx) => {
// Case 2: The node is an internal node and the key exists in it.

Expand Down Expand Up @@ -1310,10 +1310,40 @@ mod test {
}
}

macro_rules! verify_and_run {
($runner:ident, $K:ty, $V:ty) => {{
/// Asserts that the associated `BOUND` for `$ty` is _not_ `Unbounded`.
macro_rules! assert_bounded {
($ty:ty) => {
assert_ne!(<$ty>::BOUND, StorableBound::Unbounded, "Must be Bounded");
};
}

/// Asserts that the associated `BOUND` for `$ty` _is_ `Unbounded`.
macro_rules! assert_unbounded {
($ty:ty) => {
assert_eq!(<$ty>::BOUND, StorableBound::Unbounded, "Must be Unbounded");
};
}

macro_rules! run_with_key {
($runner:ident, $K:ty) => {{
verify_monotonic::<$K>();
$runner::<$K, $V>();

// Empty value.
$runner::<$K, ()>();

// Bounded values.
assert_bounded!(u32);
$runner::<$K, u32>();

assert_bounded!(Blob<20>);
$runner::<$K, Blob<20>>();

// Unbounded values.
assert_unbounded!(MonotonicVec32);
$runner::<$K, MonotonicVec32>();

assert_unbounded!(MonotonicString32);
$runner::<$K, MonotonicString32>();
}};
}

Expand All @@ -1322,37 +1352,19 @@ mod test {
($name:ident, $runner:ident) => {
#[test]
fn $name() {
use StorableBound::Unbounded;

// Set, empty value, bounded.
{
type Value = ();
assert_ne!(<Value>::BOUND, Unbounded, "Must be Bounded");
verify_and_run!($runner, u32, Value);
verify_and_run!($runner, Blob<10>, Value);
verify_and_run!($runner, MonotonicVec32, Value);
verify_and_run!($runner, MonotonicString32, Value);
}
// Bounded keys.
assert_bounded!(u32);
run_with_key!($runner, u32);

// Map, bounded value.
{
type Value = u32;
assert_ne!(Value::BOUND, Unbounded, "Must be Bounded");
verify_and_run!($runner, u32, Value);
verify_and_run!($runner, Blob<10>, Value);
verify_and_run!($runner, MonotonicVec32, Value);
verify_and_run!($runner, MonotonicString32, Value);
}
assert_bounded!(Blob<10>);
run_with_key!($runner, Blob<10>);

// Map, unbounded value.
{
type Value = MonotonicVec32;
assert_eq!(Value::BOUND, Unbounded, "Must be Unbounded");
verify_and_run!($runner, u32, Value);
verify_and_run!($runner, Blob<10>, Value);
verify_and_run!($runner, MonotonicVec32, Value);
verify_and_run!($runner, MonotonicString32, Value);
}
// Unbounded keys.
assert_unbounded!(MonotonicVec32);
run_with_key!($runner, MonotonicVec32);

assert_unbounded!(MonotonicString32);
run_with_key!($runner, MonotonicString32);
}
};
}
Expand Down Expand Up @@ -1440,7 +1452,7 @@ mod test {
assert!(right_child.is_full());
let median_index = right_child.entries_len() / 2;
let median_key = key(12);
assert_eq!(right_child.key(median_index), &median_key);
assert_eq!(right_child.key(median_index, btree.memory()), &median_key);

// Overwrite the value of the median key.
assert_eq!(btree.insert(median_key.clone(), value(123)), Some(value(0)));
Expand Down Expand Up @@ -3089,7 +3101,7 @@ mod test {
// [0, 1, 2, 3, 4, 5] [7, 8, 9, 10, 11]
let root = btree.load_node(btree.root_addr);
assert_eq!(root.node_type(), NodeType::Internal);
assert_eq!(root.keys(), vec![vec![6; 10_000]]);
assert_eq!(root.keys(btree.memory()), vec![vec![6; 10_000]]);
assert_eq!(root.children_len(), 2);

// Remove the element in the root.
Expand All @@ -3101,7 +3113,7 @@ mod test {
// [0, 1, 2, 3, 4] [7, 8, 9, 10, 11]
let root = btree.load_node(btree.root_addr);
assert_eq!(root.node_type(), NodeType::Internal);
assert_eq!(root.keys(), vec![vec![5; 10_000]]);
assert_eq!(root.keys(btree.memory()), vec![vec![5; 10_000]]);
assert_eq!(root.children_len(), 2);

// Remove the element in the root. This triggers the case where the root
Expand All @@ -3113,7 +3125,7 @@ mod test {
let root = btree.load_node(btree.root_addr);
assert_eq!(root.node_type(), NodeType::Leaf);
assert_eq!(
root.keys(),
root.keys(btree.memory()),
vec![
vec![0; 10_000],
vec![1; 10_000],
Expand Down
29 changes: 17 additions & 12 deletions src/btreemap/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ where
Bound::Included(key) | Bound::Excluded(key) => {
let mut node = self.map.load_node(self.map.root_addr);
loop {
match node.search(key) {
match node.search(key, self.map.memory()) {
Ok(idx) => {
if let Bound::Included(_) = self.range.start_bound() {
// We found the key exactly matching the left bound.
Expand All @@ -115,7 +115,7 @@ where
};

if idx + 1 < node.entries_len()
&& self.range.contains(node.key(idx + 1))
&& self.range.contains(node.key(idx + 1, self.map.memory()))
{
self.forward_cursors.push(Cursor::Node {
node,
Expand Down Expand Up @@ -152,7 +152,9 @@ where
NodeType::Leaf => None,
};

if idx < node.entries_len() && self.range.contains(node.key(idx)) {
if idx < node.entries_len()
&& self.range.contains(node.key(idx, self.map.memory()))
{
self.forward_cursors.push(Cursor::Node {
node,
next: Index::Entry(idx),
Expand Down Expand Up @@ -188,7 +190,7 @@ where
Bound::Included(key) | Bound::Excluded(key) => {
let mut node = self.map.load_node(self.map.root_addr);
loop {
match node.search(key) {
match node.search(key, self.map.memory()) {
Ok(idx) => {
if let Bound::Included(_) = self.range.end_bound() {
// We found the key exactly matching the right bound.
Expand All @@ -208,7 +210,9 @@ where
NodeType::Leaf => None,
};

if idx > 0 && self.range.contains(node.key(idx - 1)) {
if idx > 0
&& self.range.contains(node.key(idx - 1, self.map.memory()))
{
self.backward_cursors.push(Cursor::Node {
node,
next: Index::Entry(idx - 1),
Expand Down Expand Up @@ -243,7 +247,8 @@ where
NodeType::Leaf => None,
};

if idx > 0 && self.range.contains(node.key(idx - 1)) {
if idx > 0 && self.range.contains(node.key(idx - 1, self.map.memory()))
{
self.backward_cursors.push(Cursor::Node {
node,
next: Index::Entry(idx - 1),
Expand Down Expand Up @@ -320,15 +325,15 @@ where
next: Index::Entry(entry_idx),
} => {
// If the key does not belong to the range, iteration stops.
if !self.range.contains(node.key(entry_idx)) {
if !self.range.contains(node.key(entry_idx, self.map.memory())) {
// Clear all cursors to avoid needless work in subsequent calls.
self.forward_cursors = vec![];
self.backward_cursors = vec![];
return None;
}

let res = map(&node, entry_idx);
self.range.0 = Bound::Excluded(node.key(entry_idx).clone());
self.range.0 = Bound::Excluded(node.key(entry_idx, self.map.memory()).clone());

let next = match node.node_type() {
// If this is an internal node, add the next child to the cursors.
Expand Down Expand Up @@ -403,15 +408,15 @@ where
next: Index::Entry(entry_idx),
} => {
// If the key does not belong to the range, iteration stops.
if !self.range.contains(node.key(entry_idx)) {
if !self.range.contains(node.key(entry_idx, self.map.memory())) {
// Clear all cursors to avoid needless work in subsequent calls.
self.forward_cursors = vec![];
self.backward_cursors = vec![];
return None;
}

let res = map(&node, entry_idx);
self.range.1 = Bound::Excluded(node.key(entry_idx).clone());
self.range.1 = Bound::Excluded(node.key(entry_idx, self.map.memory()).clone());

if let Some(next) = match node.node_type() {
// If this is an internal node, add the previous child to the cursors.
Expand Down Expand Up @@ -497,7 +502,7 @@ where

fn next(&mut self) -> Option<Self::Item> {
self.0
.next_map(|node, entry_idx| node.key(entry_idx).clone())
.next_map(|node, entry_idx| node.key(entry_idx, self.0.map.memory()).clone())
}

fn count(mut self) -> usize
Expand All @@ -516,7 +521,7 @@ where
{
fn next_back(&mut self) -> Option<Self::Item> {
self.0
.next_back_map(|node, entry_idx| node.key(entry_idx).clone())
.next_back_map(|node, entry_idx| node.key(entry_idx, self.0.map.memory()).clone())
}
}

Expand Down
Loading
Loading