Skip to content

Commit bf3d024

Browse files
committed
BTree: let clear recycle root memory
1 parent d40f24e commit bf3d024

File tree

5 files changed

+109
-33
lines changed

5 files changed

+109
-33
lines changed

library/alloc/src/collections/btree/map.rs

+23-12
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::alloc::{Allocator, Global};
1313

1414
use super::borrow::DormantMutRef;
1515
use super::dedup_sorted_iter::DedupSortedIter;
16-
use super::navigate::{LazyLeafRange, LeafRange};
16+
use super::navigate::{LazyLeafRange, LeafRange, RootVessel};
1717
use super::node::{self, marker, ForceResult::*, Handle, NodeRef, Root};
1818
use super::search::SearchResult::*;
1919

@@ -559,6 +559,7 @@ impl<K, V> BTreeMap<K, V> {
559559

560560
impl<K, V, A: Allocator> BTreeMap<K, V, A> {
561561
/// Clears the map, removing all elements.
562+
/// Keeps a part of the allocated memory for reuse.
562563
///
563564
/// # Examples
564565
///
@@ -574,12 +575,18 @@ impl<K, V, A: Allocator> BTreeMap<K, V, A> {
574575
/// ```
575576
#[stable(feature = "rust1", since = "1.0.0")]
576577
pub fn clear(&mut self) {
577-
// avoid moving the allocator
578-
mem::drop(BTreeMap {
579-
root: mem::replace(&mut self.root, None),
580-
length: mem::replace(&mut self.length, 0),
581-
alloc: ManuallyDrop::new(&*self.alloc),
582-
});
578+
if let Some(root) = self.root.take() {
579+
let mut iter = IntoIter {
580+
range: root.into_dying().full_range(),
581+
length: self.length,
582+
alloc: unsafe { ManuallyDrop::take(&mut self.alloc) },
583+
};
584+
self.length = 0;
585+
while let Some(kv) = iter.dying_next(Some(&mut self.root)) {
586+
// SAFETY: we consume the dying handle immediately.
587+
unsafe { kv.drop_key_val() };
588+
}
589+
}
583590
}
584591

585592
/// Makes a new empty BTreeMap with a reasonable choice for B.
@@ -1606,14 +1613,14 @@ impl<K, V, A: Allocator> Drop for IntoIter<K, V, A> {
16061613
fn drop(&mut self) {
16071614
// Continue the same loop we perform below. This only runs when unwinding, so we
16081615
// don't have to care about panics this time (they'll abort).
1609-
while let Some(kv) = self.0.dying_next() {
1616+
while let Some(kv) = self.0.dying_next(None) {
16101617
// SAFETY: we consume the dying handle immediately.
16111618
unsafe { kv.drop_key_val() };
16121619
}
16131620
}
16141621
}
16151622

1616-
while let Some(kv) = self.dying_next() {
1623+
while let Some(kv) = self.dying_next(None) {
16171624
let guard = DropGuard(self);
16181625
// SAFETY: we don't touch the tree before consuming the dying handle.
16191626
unsafe { kv.drop_key_val() };
@@ -1625,11 +1632,15 @@ impl<K, V, A: Allocator> Drop for IntoIter<K, V, A> {
16251632
impl<K, V, A: Allocator> IntoIter<K, V, A> {
16261633
/// Core of a `next` method returning a dying KV handle,
16271634
/// invalidated by further calls to this function and some others.
1635+
///
1636+
/// If `root_recycling` is given some vessel, this method recycles the last
1637+
/// leaf and stores it as a fresh root in the vessel.
16281638
fn dying_next(
16291639
&mut self,
1640+
root_recycling: Option<&mut RootVessel<K, V>>,
16301641
) -> Option<Handle<NodeRef<marker::Dying, K, V, marker::LeafOrInternal>, marker::KV>> {
16311642
if self.length == 0 {
1632-
self.range.deallocating_end(&self.alloc);
1643+
self.range.deallocating_end(&self.alloc, root_recycling);
16331644
None
16341645
} else {
16351646
self.length -= 1;
@@ -1643,7 +1654,7 @@ impl<K, V, A: Allocator> IntoIter<K, V, A> {
16431654
&mut self,
16441655
) -> Option<Handle<NodeRef<marker::Dying, K, V, marker::LeafOrInternal>, marker::KV>> {
16451656
if self.length == 0 {
1646-
self.range.deallocating_end(&self.alloc);
1657+
self.range.deallocating_end(&self.alloc, None);
16471658
None
16481659
} else {
16491660
self.length -= 1;
@@ -1658,7 +1669,7 @@ impl<K, V, A: Allocator> Iterator for IntoIter<K, V, A> {
16581669

16591670
fn next(&mut self) -> Option<(K, V)> {
16601671
// SAFETY: we consume the dying handle immediately.
1661-
self.dying_next().map(unsafe { |kv| kv.into_key_val() })
1672+
self.dying_next(None).map(unsafe { |kv| kv.into_key_val() })
16621673
}
16631674

16641675
fn size_hint(&self) -> (usize, Option<usize>) {

library/alloc/src/collections/btree/map/tests.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1408,14 +1408,16 @@ fn test_bad_zst() {
14081408
#[test]
14091409
fn test_clear() {
14101410
let mut map = BTreeMap::new();
1411+
map.clear();
1412+
assert_eq!(map.height(), None);
14111413
for &len in &[MIN_INSERTS_HEIGHT_1, MIN_INSERTS_HEIGHT_2, 0, node::CAPACITY] {
14121414
for i in 0..len {
14131415
map.insert(i, ());
14141416
}
14151417
assert_eq!(map.len(), len);
14161418
map.clear();
14171419
map.check();
1418-
assert_eq!(map.height(), None);
1420+
assert_eq!(map.height(), Some(0), "len={len}");
14191421
}
14201422
}
14211423

library/alloc/src/collections/btree/navigate.rs

+32-7
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use core::hint;
33
use core::ops::RangeBounds;
44
use core::ptr;
55

6-
use super::node::{marker, ForceResult::*, Handle, NodeRef};
6+
use super::node::{marker, ForceResult::*, Handle, NodeRef, Root};
77

88
use crate::alloc::Allocator;
99
// `front` and `back` are always both `None` or both `Some`.
@@ -12,6 +12,8 @@ pub struct LeafRange<BorrowType, K, V> {
1212
back: Option<Handle<NodeRef<BorrowType, K, V, marker::Leaf>, marker::Edge>>,
1313
}
1414

15+
pub type RootVessel<K, V> = Option<Root<K, V>>;
16+
1517
impl<'a, K: 'a, V: 'a> Clone for LeafRange<marker::Immut<'a>, K, V> {
1618
fn clone(&self) -> Self {
1719
LeafRange { front: self.front.clone(), back: self.back.clone() }
@@ -198,9 +200,14 @@ impl<K, V> LazyLeafRange<marker::Dying, K, V> {
198200
}
199201

200202
#[inline]
201-
pub fn deallocating_end<A: Allocator>(&mut self, alloc: &A) {
203+
/// Fused: no harm if invoked multiple times on the same range object.
204+
pub fn deallocating_end<A: Allocator>(
205+
&mut self,
206+
alloc: &A,
207+
root_recycling: Option<&mut RootVessel<K, V>>,
208+
) {
202209
if let Some(front) = self.take_front() {
203-
front.deallocating_end(alloc)
210+
front.deallocating_end(alloc, root_recycling)
204211
}
205212
}
206213
}
@@ -501,10 +508,28 @@ impl<K, V> Handle<NodeRef<marker::Dying, K, V, marker::Leaf>, marker::Edge> {
501508
/// both sides of the tree, and have hit the same edge. As it is intended
502509
/// only to be called when all keys and values have been returned,
503510
/// no cleanup is done on any of the keys or values.
504-
fn deallocating_end<A: Allocator>(self, alloc: &A) {
505-
let mut edge = self.forget_node_type();
506-
while let Some(parent_edge) = unsafe { edge.into_node().deallocate_and_ascend(alloc) } {
507-
edge = parent_edge.forget_node_type();
511+
///
512+
/// If `root_recycling` is given some vessel, this method recycles the leaf
513+
/// and stores it as a fresh root in the vessel, instead of deallocating it.
514+
fn deallocating_end<A: Allocator>(
515+
self,
516+
alloc: &A,
517+
root_recycling: Option<&mut RootVessel<K, V>>,
518+
) {
519+
let leaf = self.into_node();
520+
let mut parent_edge = match root_recycling {
521+
None => unsafe { leaf.deallocate_and_ascend(alloc) },
522+
Some(root_recycling) => {
523+
let (leaf, parent_edge) = unsafe { leaf.recycle_and_ascend() };
524+
*root_recycling = Some(leaf.forget_type());
525+
parent_edge
526+
}
527+
};
528+
loop {
529+
parent_edge = match parent_edge {
530+
Some(edge) => unsafe { edge.into_node().deallocate_and_ascend(alloc) },
531+
None => return,
532+
}
508533
}
509534
}
510535
}

library/alloc/src/collections/btree/node.rs

+50-13
Original file line numberDiff line numberDiff line change
@@ -394,20 +394,57 @@ impl<K, V> NodeRef<marker::Dying, K, V, marker::LeafOrInternal> {
394394
self,
395395
alloc: &A,
396396
) -> Option<Handle<NodeRef<marker::Dying, K, V, marker::Internal>, marker::Edge>> {
397-
let height = self.height;
398-
let node = self.node;
399-
let ret = self.ascend().ok();
400-
unsafe {
401-
alloc.deallocate(
402-
node.cast(),
403-
if height > 0 {
404-
Layout::new::<InternalNode<K, V>>()
405-
} else {
406-
Layout::new::<LeafNode<K, V>>()
407-
},
408-
);
397+
match self.force() {
398+
ForceResult::Leaf(node) => unsafe { node.deallocate_and_ascend(alloc) },
399+
ForceResult::Internal(node) => unsafe { node.deallocate_and_ascend(alloc) },
409400
}
410-
ret
401+
}
402+
}
403+
404+
impl<K, V> NodeRef<marker::Dying, K, V, marker::Internal> {
405+
/// Overload for internal nodes.
406+
pub unsafe fn deallocate_and_ascend<A: Allocator>(
407+
self,
408+
alloc: &A,
409+
) -> Option<Handle<NodeRef<marker::Dying, K, V, marker::Internal>, marker::Edge>> {
410+
debug_assert!(self.height > 0);
411+
let node = self.node;
412+
let parent_edge = self.ascend().ok();
413+
unsafe { alloc.deallocate(node.cast(), Layout::new::<InternalNode<K, V>>()) };
414+
parent_edge
415+
}
416+
}
417+
418+
impl<K, V> NodeRef<marker::Dying, K, V, marker::Leaf> {
419+
/// Overload for leaf nodes.
420+
pub unsafe fn deallocate_and_ascend<A: Allocator>(
421+
self,
422+
alloc: &A,
423+
) -> Option<Handle<NodeRef<marker::Dying, K, V, marker::Internal>, marker::Edge>> {
424+
debug_assert!(self.height == 0);
425+
let node = self.node;
426+
let parent_edge = self.ascend().ok();
427+
unsafe { alloc.deallocate(node.cast(), Layout::new::<LeafNode<K, V>>()) };
428+
parent_edge
429+
}
430+
}
431+
432+
impl<K, V> NodeRef<marker::Dying, K, V, marker::Leaf> {
433+
/// Similar to `ascend`, gets a reference to a node's parent node, but also
434+
/// clears and returns the current leaf node. This is unsafe because that
435+
/// leaf node will still be accessible from the dying tree, despite having
436+
/// been reinitialized and being returned in an exclusive `NodeRef`.
437+
pub unsafe fn recycle_and_ascend(
438+
self,
439+
) -> (
440+
NodeRef<marker::Owned, K, V, marker::Leaf>,
441+
Option<Handle<NodeRef<marker::Dying, K, V, marker::Internal>, marker::Edge>>,
442+
) {
443+
debug_assert!(self.height == 0);
444+
let node = self.node;
445+
let parent_edge = self.ascend().ok();
446+
unsafe { LeafNode::init(node.as_ptr()) };
447+
(NodeRef { height: 0, node, _marker: PhantomData }, parent_edge)
411448
}
412449
}
413450

library/alloc/src/collections/btree/set.rs

+1
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,7 @@ impl<T, A: Allocator> BTreeSet<T, A> {
572572
}
573573

574574
/// Clears the set, removing all elements.
575+
/// Keeps a part of the allocated memory for reuse.
575576
///
576577
/// # Examples
577578
///

0 commit comments

Comments
 (0)