Skip to content

Commit b069a5e

Browse files
committed
In-place growth for HashMap.
1 parent 2f15f7d commit b069a5e

File tree

2 files changed

+73
-8
lines changed

2 files changed

+73
-8
lines changed

src/libstd/collections/hash/map.rs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use iter::{self, Iterator, ExactSizeIterator, IntoIterator, IteratorExt, FromIte
2323
use marker::Sized;
2424
use mem::{self, replace};
2525
use num::{Int, UnsignedInt};
26-
use ops::{Drop, FnMut, Index, IndexMut};
26+
use ops::{Deref, DerefMut, Drop, FnMut, Index, IndexMut};
2727
use option::Option::{self, Some, None};
2828
use rand::{self, Rng};
2929
use result::Result::{self, Ok, Err};
@@ -661,12 +661,20 @@ impl<K, V, S, H> HashMap<K, V, S>
661661
}
662662

663663
// Grow the table.
664-
let mut destination = RawTable::new(new_capacity);
664+
let is_inplace = self.table.grow_inplace(new_capacity);
665+
666+
let mut destination = if is_inplace {
667+
// Resizing in-place.
668+
None
669+
} else {
670+
// Borrow self.table in both branches to satisfy the checker.
671+
Some(RawTable::new(new_capacity))
672+
};
665673

666674
// Iterate over `old_capacity` buckets, which constitute half of
667675
// the table which was resized in-place, or the entire
668676
// `old_table`.
669-
let mut bucket = Bucket::at_index(&mut self.table, 0).ok().unwrap();
677+
let mut bucket = Bucket::at_index(&mut self.table, 0).ok().unwrap().iter_to(old_capacity);
670678

671679
// "So a few of the first shall be last: for many be called,
672680
// but few chosen."
@@ -717,15 +725,23 @@ impl<K, V, S, H> HashMap<K, V, S>
717725
Full(bucket) => {
718726
let h = *bucket.read().0;
719727
let (b, k, v) = bucket.take();
720-
insert_hashed_ordered(&mut destination, h, k, v);
721-
b.into_bucket()
728+
729+
if let Some(ref mut dest) = destination {
730+
insert_hashed_ordered(dest, h, k, v);
731+
b.into_bucket()
732+
} else {
733+
// Resizing in-place.
734+
insert_hashed_ordered(b.into_bucket(), h, k, v)
735+
}
722736
}
723737
Empty(b) => b.into_bucket()
724738
};
725739
bucket.next(); // wraps at old_capacity
726740
}
727741

728-
replace(bucket.into_table(), destination);
742+
if let Some(dest) = destination {
743+
replace(bucket.into_table(), dest);
744+
}
729745
}
730746

731747
/// Shrinks the capacity of the map as much as possible. It will drop

src/libstd/collections/hash/table.rs

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ use mem::{self, min_align_of, size_of};
2222
use num::{Int, UnsignedInt};
2323
use ops::{Deref, DerefMut, Drop};
2424
use option::Option::{self, Some, None};
25-
use ptr::{self, Unique, PtrExt, copy_nonoverlapping_memory, zero_memory};
25+
use ptr::{self, Unique, PtrExt, copy_memory, copy_nonoverlapping_memory, zero_memory};
2626
use result::Result::{self, Ok, Err};
27-
use rt::heap::{allocate, deallocate};
27+
use rt::heap::{allocate, deallocate, reallocate_inplace};
2828
use collections::hash_state::HashState;
2929
use core::nonzero::NonZero;
3030

@@ -277,6 +277,14 @@ impl<K, V, M> Bucket<K, V, M> where RawTable<K, V>: BorrowFrom<M> {
277277
Bucket::at_index(table, 0).map(|b| b.into_iter()).unwrap_or_else(|b| b.into_iter())
278278
}
279279

280+
/// Narrows down the range of iteration, which must be a power of 2.
281+
pub fn iter_to(mut self, limit: usize) -> Bucket<K, V, M> {
282+
assert!(limit <= self.0.table.capacity());
283+
assert!(limit.is_power_of_two());
284+
self.0.capacity = limit;
285+
self
286+
}
287+
280288
/// Reads a bucket at a given index, returning an enum indicating whether
281289
/// it's initialized or not. You need to match on this enum to get
282290
/// the appropriate types to call most of the other functions in
@@ -521,6 +529,47 @@ impl<K, V> RawTable<K, V> {
521529
}
522530
}
523531

532+
pub fn grow_inplace(&mut self, capacity: uint) -> bool {
533+
assert!(capacity.is_power_of_two());
534+
assert!(capacity >= self.capacity);
535+
536+
if self.middle.ptr.is_null() {
537+
return false;
538+
}
539+
540+
let new_size = checked_size_generic::<K, V>(capacity);
541+
542+
unsafe {
543+
let ptr = self.middle.ptr.offset(-(self.capacity as isize)) as *mut u8;
544+
let is_inplace = reallocate_inplace(ptr,
545+
size_generic::<K, V>(self.capacity),
546+
new_size,
547+
align::<K, V>()) >= new_size;
548+
549+
if is_inplace {
550+
let cap_diff = (capacity - self.capacity) as isize;
551+
let hashes = self.middle.ptr.offset(cap_diff) as *mut Option<SafeHash>;
552+
// Copy the array of hashes. Maybe it's already in cache.
553+
if size_of::<(K, V)>() >= size_of::<Option<SafeHash>>() {
554+
// The regions of memory occupied by old and new hash arrays are disjoint.
555+
// before: [KVKVKVKV|h h h h ]
556+
// after: [KVKVKVKV|KVKVKVKV|h h h h h h h h ]
557+
copy_nonoverlapping_memory(hashes, self.middle.ptr as *const _, self.capacity);
558+
} else {
559+
// before: [KVKVKVKV|h h |h h ]
560+
// after: [KVKVKVKV|KVKVKVKV|h h h h h h h h ]
561+
copy_memory(hashes, self.middle.ptr as *const _, self.capacity);
562+
}
563+
zero_memory(hashes.offset(self.capacity as int), capacity - self.capacity);
564+
565+
self.middle = Unique(self.middle.ptr.offset(cap_diff));
566+
self.capacity = capacity;
567+
}
568+
569+
return is_inplace;
570+
}
571+
}
572+
524573
/// The hashtable's capacity, similar to a vector's.
525574
pub fn capacity(&self) -> usize {
526575
self.capacity

0 commit comments

Comments
 (0)