Skip to content
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

BTreeMap: stop mistaking node::MIN_LEN for a node level constraint #78322

Merged
merged 1 commit into from
Oct 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 6 additions & 2 deletions library/alloc/src/collections/btree/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ mod entry;
pub use entry::{Entry, OccupiedEntry, VacantEntry};
use Entry::*;

/// Minimum number of elements in nodes that are not a root.
/// We might temporarily have fewer elements during methods.
pub(super) const MIN_LEN: usize = node::MIN_LEN_AFTER_SPLIT;

/// A map based on a B-Tree.
///
/// B-Trees represent a fundamental compromise between cache-efficiency and actually minimizing
Expand Down Expand Up @@ -1094,13 +1098,13 @@ impl<K: Ord, V> BTreeMap<K, V> {
// Check if right-most child is underfull.
let mut last_edge = internal.last_edge();
let right_child_len = last_edge.reborrow().descend().len();
if right_child_len < node::MIN_LEN {
if right_child_len < MIN_LEN {
// We need to steal.
let mut last_kv = match last_edge.left_kv() {
Ok(left) => left,
Err(_) => unreachable!(),
};
last_kv.bulk_steal_left(node::MIN_LEN - right_child_len);
last_kv.bulk_steal_left(MIN_LEN - right_child_len);
last_edge = last_kv.right_edge();
}

Expand Down
21 changes: 19 additions & 2 deletions library/alloc/src/collections/btree/map/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,15 @@ impl<'a, K: 'a, V: 'a> BTreeMap<K, V> {
{
if let Some(root) = &self.root {
let root_node = root.node_as_ref();

assert!(root_node.ascend().is_err());
root_node.assert_back_pointers();
root_node.assert_ascending();
assert_eq!(self.length, root_node.assert_and_add_lengths());

let counted = root_node.assert_ascending();
assert_eq!(self.length, counted);
assert_eq!(self.length, root_node.calc_length());

root_node.assert_min_len(if root_node.height() > 0 { 1 } else { 0 });
} else {
assert_eq!(self.length, 0);
}
Expand All @@ -76,6 +81,18 @@ impl<'a, K: 'a, V: 'a> BTreeMap<K, V> {
}
}

impl<'a, K: 'a, V: 'a> NodeRef<marker::Immut<'a>, K, V, marker::LeafOrInternal> {
pub fn assert_min_len(self, min_len: usize) {
assert!(self.len() >= min_len, "{} < {}", self.len(), min_len);
if let node::ForceResult::Internal(node) = self.force() {
for idx in 0..=node.len() {
let edge = unsafe { Handle::new_edge(node, idx) };
edge.descend().assert_min_len(MIN_LEN);
}
}
}
}

// Test our value of MIN_INSERTS_HEIGHT_2. It may change according to the
// implementation of insertion, but it's best to be aware of when it does.
#[test]
Expand Down
2 changes: 1 addition & 1 deletion library/alloc/src/collections/btree/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ use crate::alloc::{AllocRef, Global, Layout};
use crate::boxed::Box;

const B: usize = 6;
pub const MIN_LEN: usize = B - 1;
pub const CAPACITY: usize = 2 * B - 1;
pub const MIN_LEN_AFTER_SPLIT: usize = B - 1;
const KV_IDX_CENTER: usize = B - 1;
const EDGE_IDX_LEFT_OF_CENTER: usize = B - 1;
const EDGE_IDX_RIGHT_OF_CENTER: usize = B;
Expand Down
56 changes: 16 additions & 40 deletions library/alloc/src/collections/btree/node/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,26 @@ use crate::string::String;
use core::cmp::Ordering::*;

impl<'a, K: 'a, V: 'a> NodeRef<marker::Immut<'a>, K, V, marker::LeafOrInternal> {
/// Asserts that the back pointer in each reachable node points to its parent.
pub fn assert_back_pointers(self) {
match self.force() {
ForceResult::Leaf(_) => {}
ForceResult::Internal(node) => {
for idx in 0..=node.len() {
let edge = unsafe { Handle::new_edge(node, idx) };
let child = edge.descend();
assert!(child.ascend().ok() == Some(edge));
child.assert_back_pointers();
}
if let ForceResult::Internal(node) = self.force() {
for idx in 0..=node.len() {
let edge = unsafe { Handle::new_edge(node, idx) };
let child = edge.descend();
assert!(child.ascend().ok() == Some(edge));
child.assert_back_pointers();
}
}
}

pub fn assert_ascending(self)
/// Asserts that the keys are in strictly ascending order.
/// Returns how many keys it encountered.
pub fn assert_ascending(self) -> usize
where
K: Copy + Debug + Ord,
{
struct SeriesChecker<T> {
num_seen: usize,
previous: Option<T>,
}
impl<T: Copy + Debug + Ord> SeriesChecker<T> {
Expand All @@ -32,10 +33,11 @@ impl<'a, K: 'a, V: 'a> NodeRef<marker::Immut<'a>, K, V, marker::LeafOrInternal>
assert!(previous < next, "{:?} >= {:?}", previous, next);
}
self.previous = Some(next);
self.num_seen += 1;
}
}

let mut checker = SeriesChecker { previous: None };
let mut checker = SeriesChecker { num_seen: 0, previous: None };
self.visit_nodes_in_order(|pos| match pos {
navigate::Position::Leaf(node) => {
for idx in 0..node.len() {
Expand All @@ -49,33 +51,7 @@ impl<'a, K: 'a, V: 'a> NodeRef<marker::Immut<'a>, K, V, marker::LeafOrInternal>
}
navigate::Position::Internal(_) => {}
});
}

pub fn assert_and_add_lengths(self) -> usize {
let mut internal_length = 0;
let mut internal_kv_count = 0;
let mut leaf_length = 0;
self.visit_nodes_in_order(|pos| match pos {
navigate::Position::Leaf(node) => {
let is_root = self.height() == 0;
let min_len = if is_root { 0 } else { MIN_LEN };
assert!(node.len() >= min_len, "{} < {}", node.len(), min_len);
leaf_length += node.len();
}
navigate::Position::Internal(node) => {
let is_root = self.height() == node.height();
let min_len = if is_root { 1 } else { MIN_LEN };
assert!(node.len() >= min_len, "{} < {}", node.len(), min_len);
internal_length += node.len();
}
navigate::Position::InternalKV(_) => {
internal_kv_count += 1;
}
});
assert_eq!(internal_length, internal_kv_count);
let total = internal_length + leaf_length;
assert_eq!(self.calc_length(), total);
total
checker.num_seen
}

pub fn dump_keys(self) -> String
Expand Down Expand Up @@ -124,8 +100,8 @@ fn test_splitpoint() {
right_len += 1;
}
}
assert!(left_len >= MIN_LEN);
assert!(right_len >= MIN_LEN);
assert!(left_len >= MIN_LEN_AFTER_SPLIT);
assert!(right_len >= MIN_LEN_AFTER_SPLIT);
assert!(left_len + right_len == CAPACITY);
}
}
Expand Down
5 changes: 3 additions & 2 deletions library/alloc/src/collections/btree/remove.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::node::{self, marker, ForceResult, Handle, NodeRef};
use super::map::MIN_LEN;
use super::node::{marker, ForceResult, Handle, NodeRef};
use super::unwrap_unchecked;
use core::mem;
use core::ptr;
Expand Down Expand Up @@ -40,7 +41,7 @@ impl<'a, K: 'a, V: 'a> Handle<NodeRef<marker::Mut<'a>, K, V, marker::LeafOrInter
// Handle underflow
let mut cur_node = unsafe { ptr::read(&pos).into_node().forget_type() };
let mut at_leaf = true;
while cur_node.len() < node::MIN_LEN {
while cur_node.len() < MIN_LEN {
match handle_underfull_node(cur_node) {
UnderflowResult::AtRoot => break,
UnderflowResult::Merged(edge, merged_with_left, offset) => {
Expand Down
18 changes: 10 additions & 8 deletions library/alloc/src/collections/btree/split.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::node::{self, ForceResult::*, Root};
use super::search::{self, SearchResult::*};
use super::map::MIN_LEN;
use super::node::{ForceResult::*, Root};
use super::search::{search_node, SearchResult::*};
use core::borrow::Borrow;

impl<K, V> Root<K, V> {
Expand All @@ -20,7 +21,7 @@ impl<K, V> Root<K, V> {
let mut right_node = right_root.node_as_mut();

loop {
let mut split_edge = match search::search_node(left_node, key) {
let mut split_edge = match search_node(left_node, key) {
// key is going to the right tree
Found(handle) => handle.left_edge(),
GoDown(handle) => handle,
Expand Down Expand Up @@ -65,9 +66,9 @@ impl<K, V> Root<K, V> {
cur_node = last_kv.merge().descend();
} else {
let right_len = last_kv.reborrow().right_edge().descend().len();
// `MINLEN + 1` to avoid readjust if merge happens on the next level.
if right_len < node::MIN_LEN + 1 {
last_kv.bulk_steal_left(node::MIN_LEN + 1 - right_len);
// `MIN_LEN + 1` to avoid readjust if merge happens on the next level.
if right_len < MIN_LEN + 1 {
last_kv.bulk_steal_left(MIN_LEN + 1 - right_len);
}
cur_node = last_kv.right_edge().descend();
}
Expand All @@ -91,8 +92,9 @@ impl<K, V> Root<K, V> {
cur_node = first_kv.merge().descend();
} else {
let left_len = first_kv.reborrow().left_edge().descend().len();
if left_len < node::MIN_LEN + 1 {
first_kv.bulk_steal_right(node::MIN_LEN + 1 - left_len);
// `MIN_LEN + 1` to avoid readjust if merge happens on the next level.
if left_len < MIN_LEN + 1 {
first_kv.bulk_steal_right(MIN_LEN + 1 - left_len);
}
cur_node = first_kv.left_edge().descend();
}
Expand Down