Skip to content

Commit ef6060c

Browse files
committed
Add a statically allocated empty node for empty maps
This gives a pointer to that static empty node instead of allocating a new node, and then whenever inserting makes sure that the root isn't that empty node.
1 parent 669bd82 commit ef6060c

File tree

2 files changed

+43
-2
lines changed

2 files changed

+43
-2
lines changed

src/liballoc/btree/map.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,7 @@ impl<K: Ord, V> BTreeMap<K, V> {
523523
#[stable(feature = "rust1", since = "1.0.0")]
524524
pub fn new() -> BTreeMap<K, V> {
525525
BTreeMap {
526-
root: node::Root::new_leaf(),
526+
root: node::Root::shared_empty_root(),
527527
length: 0,
528528
}
529529
}
@@ -544,7 +544,6 @@ impl<K: Ord, V> BTreeMap<K, V> {
544544
/// ```
545545
#[stable(feature = "rust1", since = "1.0.0")]
546546
pub fn clear(&mut self) {
547-
// FIXME(gereeter) .clear() allocates
548547
*self = BTreeMap::new();
549548
}
550549

@@ -687,6 +686,10 @@ impl<K: Ord, V> BTreeMap<K, V> {
687686
/// ```
688687
#[stable(feature = "rust1", since = "1.0.0")]
689688
pub fn insert(&mut self, key: K, value: V) -> Option<V> {
689+
if self.root.is_shared_root() {
690+
self.root = node::Root::new_leaf();
691+
}
692+
690693
match self.entry(key) {
691694
Occupied(mut entry) => Some(entry.insert(value)),
692695
Vacant(entry) => {
@@ -890,6 +893,10 @@ impl<K: Ord, V> BTreeMap<K, V> {
890893
/// ```
891894
#[stable(feature = "rust1", since = "1.0.0")]
892895
pub fn entry(&mut self, key: K) -> Entry<K, V> {
896+
if self.root.is_shared_root() {
897+
self.root = node::Root::new_leaf();
898+
}
899+
893900
match search::search_tree(self.root.as_mut(), &key) {
894901
Found(handle) => {
895902
Occupied(OccupiedEntry {
@@ -910,6 +917,10 @@ impl<K: Ord, V> BTreeMap<K, V> {
910917
}
911918

912919
fn from_sorted_iter<I: Iterator<Item = (K, V)>>(&mut self, iter: I) {
920+
if self.root.is_shared_root() {
921+
self.root = node::Root::new_leaf();
922+
}
923+
913924
let mut cur_node = last_leaf_edge(self.root.as_mut()).into_node();
914925
// Iterate through all key-value pairs, pushing them into nodes at the right level.
915926
for (key, value) in iter {

src/liballoc/btree/node.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,18 @@ impl<K, V> LeafNode<K, V> {
103103
}
104104
}
105105

106+
// We need to implement Sync here in order to make a static instance
107+
unsafe impl Sync for LeafNode<(), ()> {}
108+
109+
// An empty node used as a placeholder for the root node, to avoid allocations
110+
static EMPTY_ROOT_NODE: LeafNode<(), ()> = LeafNode {
111+
parent: ptr::null(),
112+
parent_idx: 0,
113+
len: 0,
114+
keys: [(); CAPACITY],
115+
vals: [(); CAPACITY],
116+
};
117+
106118
/// The underlying representation of internal nodes. As with `LeafNode`s, these should be hidden
107119
/// behind `BoxedNode`s to prevent dropping uninitialized keys and values. Any pointer to an
108120
/// `InternalNode` can be directly casted to a pointer to the underlying `LeafNode` portion of the
@@ -172,6 +184,24 @@ unsafe impl<K: Sync, V: Sync> Sync for Root<K, V> { }
172184
unsafe impl<K: Send, V: Send> Send for Root<K, V> { }
173185

174186
impl<K, V> Root<K, V> {
187+
pub fn is_shared_root(&self) -> bool {
188+
ptr::eq(
189+
self.node.as_ptr().as_ptr(),
190+
&EMPTY_ROOT_NODE as *const _ as *const LeafNode<K, V>,
191+
)
192+
}
193+
194+
pub fn shared_empty_root() -> Self {
195+
Root {
196+
node: unsafe {
197+
BoxedNode::from_ptr(NonNull::new_unchecked(
198+
&EMPTY_ROOT_NODE as *const _ as *const LeafNode<K, V> as *mut _
199+
))
200+
},
201+
height: 0,
202+
}
203+
}
204+
175205
pub fn new_leaf() -> Self {
176206
Root {
177207
node: BoxedNode::from_leaf(Box::new(unsafe { LeafNode::new() })),

0 commit comments

Comments
 (0)