Skip to content

Commit 26befd8

Browse files
committed
recycle nodes to avoid allocations
1 parent 47ee7b9 commit 26befd8

File tree

1 file changed

+78
-3
lines changed

1 file changed

+78
-3
lines changed

src/lib.rs

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#![feature(hashmap_hasher)]
3232
#![feature(box_raw)]
3333
#![feature(iter_order)]
34+
#![cfg_attr(test, feature(test))]
3435

3536
use std::borrow::Borrow;
3637
use std::cmp::Ordering;
@@ -57,6 +58,7 @@ struct LinkedHashMapEntry<K, V> {
5758
pub struct LinkedHashMap<K, V, S = hash_map::RandomState> {
5859
map: HashMap<KeyRef<K>, Box<LinkedHashMapEntry<K, V>>, S>,
5960
head: *mut LinkedHashMapEntry<K, V>,
61+
free: *mut LinkedHashMapEntry<K, V>,
6062
}
6163

6264
impl<K: Hash> Hash for KeyRef<K> {
@@ -117,11 +119,26 @@ impl<K: Hash + Eq, V> LinkedHashMap<K, V> {
117119
}
118120
}
119121

122+
impl<K, V, S> LinkedHashMap<K, V, S> {
123+
fn clear_free_list(&mut self) {
124+
unsafe {
125+
let mut free = self.free;
126+
while ! free.is_null() {
127+
let next_free = (*free).next;
128+
drop_empty_entry_box(free);
129+
free = next_free;
130+
}
131+
self.free = ptr::null_mut();
132+
}
133+
}
134+
}
135+
120136
impl<K: Hash + Eq, V, S: HashState> LinkedHashMap<K, V, S> {
121137
fn with_map(map: HashMap<KeyRef<K>, Box<LinkedHashMapEntry<K, V>>, S>) -> LinkedHashMap<K, V, S> {
122138
LinkedHashMap {
123139
map: map,
124140
head: ptr::null_mut(),
141+
free: ptr::null_mut(),
125142
}
126143
}
127144

@@ -146,7 +163,10 @@ impl<K: Hash + Eq, V, S: HashState> LinkedHashMap<K, V, S> {
146163
/// Shrinks the capacity of the map as much as possible. It will drop down as much as possible
147164
/// while maintaining the internal rules and possibly leaving some space in accordance with the
148165
/// resize policy.
149-
pub fn shrink_to_fit(&mut self) { self.map.shrink_to_fit(); }
166+
pub fn shrink_to_fit(&mut self) {
167+
self.map.shrink_to_fit();
168+
self.clear_free_list();
169+
}
150170

151171
/// Inserts a key-value pair into the map. If the key already existed, the old value is
152172
/// returned.
@@ -178,7 +198,17 @@ impl<K: Hash + Eq, V, S: HashState> LinkedHashMap<K, V, S> {
178198
(node_ptr, None, Some(old_val))
179199
}
180200
None => {
181-
let mut node = Box::new(LinkedHashMapEntry::new(k, v));
201+
let mut node = if self.free.is_null() {
202+
Box::new(LinkedHashMapEntry::new(k, v))
203+
} else {
204+
// use a recycled box
205+
unsafe {
206+
let free = self.free;
207+
self.free = (*free).next;
208+
ptr::write(free, LinkedHashMapEntry::new(k, v));
209+
Box::from_raw(free)
210+
}
211+
};
182212
let node_ptr: *mut LinkedHashMapEntry<K, V> = &mut *node;
183213
(node_ptr, Some(node), None)
184214
}
@@ -295,7 +325,15 @@ impl<K: Hash + Eq, V, S: HashState> LinkedHashMap<K, V, S> {
295325
removed.map(|mut node| {
296326
let node_ptr: *mut LinkedHashMapEntry<K,V> = &mut *node;
297327
self.detach(node_ptr);
298-
node.value
328+
unsafe {
329+
// add to free list
330+
(*node_ptr).next = self.free;
331+
self.free = node_ptr;
332+
// forget the box but drop the key and return the value
333+
mem::forget(node);
334+
drop(ptr::read(&(*node_ptr).key));
335+
ptr::read(&(*node_ptr).value)
336+
}
299337
})
300338
}
301339

@@ -675,6 +713,7 @@ impl<K, V, S> Drop for LinkedHashMap<K, V, S> {
675713
if ! self.head.is_null() {
676714
drop_empty_entry_box(self.head);
677715
}
716+
self.clear_free_list();
678717
}
679718
}
680719
}
@@ -1059,4 +1098,40 @@ mod tests {
10591098
assert_eq!(map.remove(&Foo(Bar(1))), None);
10601099
assert_eq!(map.remove(&Foo(Bar(2))), None);
10611100
}
1101+
1102+
extern crate test;
1103+
1104+
#[bench]
1105+
fn not_recycled_cycling(b: &mut test::Bencher) {
1106+
let mut hash_map = LinkedHashMap::with_capacity(1000);
1107+
for i in (0usize..1000) {
1108+
hash_map.insert(i, i);
1109+
}
1110+
b.iter(|| {
1111+
for i in (0usize..1000) {
1112+
hash_map.remove(&i);
1113+
}
1114+
hash_map.clear_free_list();
1115+
for i in (0usize..1000) {
1116+
hash_map.insert(i, i);
1117+
}
1118+
})
1119+
}
1120+
1121+
#[bench]
1122+
fn recycled_cycling(b: &mut test::Bencher) {
1123+
let mut hash_map = LinkedHashMap::with_capacity(1000);
1124+
for i in (0usize..1000) {
1125+
hash_map.insert(i, i);
1126+
}
1127+
b.iter(|| {
1128+
for i in (0usize..1000) {
1129+
hash_map.remove(&i);
1130+
}
1131+
for i in (0usize..1000) {
1132+
hash_map.insert(i, i);
1133+
}
1134+
})
1135+
}
1136+
10621137
}

0 commit comments

Comments
 (0)