Skip to content

Commit ca7cb20

Browse files
committed
Merge pull request #25 from arthurprs/master
Avoid allocations on creation & recycle nodes to avoid allocations
2 parents 1ad72ee + db6c3b4 commit ca7cb20

File tree

2 files changed

+117
-20
lines changed

2 files changed

+117
-20
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22

33
name = "linked-hash-map"
4-
version = "0.0.5"
4+
version = "0.0.6"
55
license = "MIT/Apache-2.0"
66
description = "A HashMap wrapper that holds key-value pairs in insertion order"
77
authors = [

src/lib.rs

Lines changed: 116 additions & 19 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> {
@@ -100,6 +102,13 @@ impl<K, V> LinkedHashMapEntry<K, V> {
100102
}
101103
}
102104

105+
unsafe fn drop_empty_entry_box<K, V>(the_box: *mut LinkedHashMapEntry<K, V>) {
106+
// Prevent compiler from trying to drop the un-initialized key and values in the node.
107+
let LinkedHashMapEntry { key, value, .. } = *Box::from_raw(the_box);
108+
mem::forget(key);
109+
mem::forget(value);
110+
}
111+
103112
impl<K: Hash + Eq, V> LinkedHashMap<K, V> {
104113
/// Creates a linked hash map.
105114
pub fn new() -> LinkedHashMap<K, V> { LinkedHashMap::with_map(HashMap::new()) }
@@ -110,17 +119,27 @@ impl<K: Hash + Eq, V> LinkedHashMap<K, V> {
110119
}
111120
}
112121

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+
113136
impl<K: Hash + Eq, V, S: HashState> LinkedHashMap<K, V, S> {
114137
fn with_map(map: HashMap<KeyRef<K>, Box<LinkedHashMapEntry<K, V>>, S>) -> LinkedHashMap<K, V, S> {
115-
let map = LinkedHashMap {
138+
LinkedHashMap {
116139
map: map,
117-
head: unsafe{ Box::into_raw(Box::new(mem::uninitialized())) },
118-
};
119-
unsafe {
120-
(*map.head).next = map.head;
121-
(*map.head).prev = map.head;
140+
head: ptr::null_mut(),
141+
free: ptr::null_mut(),
122142
}
123-
return map;
124143
}
125144

126145
/// Creates an empty linked hash map with the given initial hash state.
@@ -144,7 +163,10 @@ impl<K: Hash + Eq, V, S: HashState> LinkedHashMap<K, V, S> {
144163
/// Shrinks the capacity of the map as much as possible. It will drop down as much as possible
145164
/// while maintaining the internal rules and possibly leaving some space in accordance with the
146165
/// resize policy.
147-
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+
}
148170

149171
/// Inserts a key-value pair into the map. If the key already existed, the old value is
150172
/// returned.
@@ -161,14 +183,32 @@ impl<K: Hash + Eq, V, S: HashState> LinkedHashMap<K, V, S> {
161183
/// assert_eq!(map[&2], "b");
162184
/// ```
163185
pub fn insert(&mut self, k: K, v: V) -> Option<V> {
186+
if self.head.is_null() {
187+
// allocate the guard node if not present
188+
unsafe {
189+
self.head = Box::into_raw(Box::new(mem::uninitialized()));
190+
(*self.head).next = self.head;
191+
(*self.head).prev = self.head;
192+
}
193+
}
164194
let (node_ptr, node_opt, old_val) = match self.map.get_mut(&KeyRef{k: &k}) {
165195
Some(node) => {
166196
let old_val = mem::replace(&mut node.value, v);
167197
let node_ptr: *mut LinkedHashMapEntry<K, V> = &mut **node;
168198
(node_ptr, None, Some(old_val))
169199
}
170200
None => {
171-
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+
};
172212
let node_ptr: *mut LinkedHashMapEntry<K, V> = &mut *node;
173213
(node_ptr, Some(node), None)
174214
}
@@ -285,7 +325,15 @@ impl<K: Hash + Eq, V, S: HashState> LinkedHashMap<K, V, S> {
285325
removed.map(|mut node| {
286326
let node_ptr: *mut LinkedHashMapEntry<K,V> = &mut *node;
287327
self.detach(node_ptr);
288-
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+
}
289337
})
290338
}
291339

@@ -405,9 +453,12 @@ impl<K: Hash + Eq, V, S: HashState> LinkedHashMap<K, V, S> {
405453
/// Clear the map of all key-value pairs.
406454
pub fn clear(&mut self) {
407455
self.map.clear();
408-
unsafe {
409-
(*self.head).prev = self.head;
410-
(*self.head).next = self.head;
456+
// update the guard node if present
457+
if ! self.head.is_null() {
458+
unsafe {
459+
(*self.head).prev = self.head;
460+
(*self.head).next = self.head;
461+
}
411462
}
412463
}
413464

@@ -430,8 +481,13 @@ impl<K: Hash + Eq, V, S: HashState> LinkedHashMap<K, V, S> {
430481
/// assert_eq!(None, iter.next());
431482
/// ```
432483
pub fn iter(&self) -> Iter<K, V> {
484+
let head = if ! self.head.is_null() {
485+
unsafe { (*self.head).prev }
486+
} else {
487+
ptr::null_mut()
488+
};
433489
Iter {
434-
head: unsafe { (*self.head).prev },
490+
head: head,
435491
tail: self.head,
436492
remaining: self.len(),
437493
marker: marker::PhantomData,
@@ -459,8 +515,13 @@ impl<K: Hash + Eq, V, S: HashState> LinkedHashMap<K, V, S> {
459515
/// assert_eq!(&17, map.get(&"a").unwrap());
460516
/// ```
461517
pub fn iter_mut(&mut self) -> IterMut<K, V> {
518+
let head = if ! self.head.is_null() {
519+
unsafe { (*self.head).prev }
520+
} else {
521+
ptr::null_mut()
522+
};
462523
IterMut {
463-
head: unsafe { (*self.head).prev },
524+
head: head,
464525
tail: self.head,
465526
remaining: self.len(),
466527
marker: marker::PhantomData,
@@ -649,10 +710,10 @@ unsafe impl<K: Sync, V: Sync, S: Sync> Sync for LinkedHashMap<K, V, S> {}
649710
impl<K, V, S> Drop for LinkedHashMap<K, V, S> {
650711
fn drop(&mut self) {
651712
unsafe {
652-
// Prevent compiler from trying to drop the un-initialized field in the sigil node.
653-
let LinkedHashMapEntry { next: _, prev: _, key: k, value: v } = *Box::from_raw(self.head);
654-
mem::forget(k);
655-
mem::forget(v);
713+
if ! self.head.is_null() {
714+
drop_empty_entry_box(self.head);
715+
}
716+
self.clear_free_list();
656717
}
657718
}
658719
}
@@ -1037,4 +1098,40 @@ mod tests {
10371098
assert_eq!(map.remove(&Foo(Bar(1))), None);
10381099
assert_eq!(map.remove(&Foo(Bar(2))), None);
10391100
}
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+
10401137
}

0 commit comments

Comments
 (0)