Skip to content

Commit 55ebbc9

Browse files
committed
In-place growth for HashMap.
1 parent a684093 commit 55ebbc9

File tree

2 files changed

+95
-23
lines changed

2 files changed

+95
-23
lines changed

src/libstd/collections/hash/map.rs

+40-17
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use iter::{Iterator, ExactSizeIterator, IntoIterator, IteratorExt, FromIterator,
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};
@@ -444,7 +444,7 @@ fn robin_hood<'a, K: 'a, V: 'a>(bucket: FullBucketMut<'a, K, V>,
444444

445445
// Performs insertion with relaxed requirements.
446446
// The caller should ensure that invariants of Robin Hood linear probing hold.
447-
fn insert_hashed_ordered<M, K, V>(arg: M, h: SafeHash, k: K, v: V) -> M
447+
fn insert_hashed_ordered<M: Put, K, V>(arg: M, h: SafeHash, k: K, v: V) -> M
448448
where RawTable<K, V>: BorrowFromMut<M>
449449
{
450450
let table = TableRef(arg);
@@ -651,12 +651,20 @@ impl<K, V, S> HashMap<K, V, S>
651651
}
652652

653653
// Grow the table.
654-
let mut destination = RawTable::new(new_capacity);
654+
let is_inplace = self.table.grow_inplace(new_capacity);
655+
656+
let mut destination = if is_inplace {
657+
// Resizing in-place.
658+
None
659+
} else {
660+
// Borrow self.table in both branches to satisfy the checker.
661+
Some(RawTable::new(new_capacity))
662+
};
655663

656664
// Iterate over `old_capacity` buckets, which constitute half of
657665
// the table which was resized in-place, or the entire
658666
// `old_table`.
659-
let mut bucket = Bucket::at_index(&mut self.table, 0).ok().unwrap();
667+
let mut bucket = Bucket::at_index(&mut self.table, 0).ok().unwrap().iter_to(old_capacity);
660668

661669
// "So a few of the first shall be last: for many be called,
662670
// but few chosen."
@@ -702,20 +710,35 @@ impl<K, V, S> HashMap<K, V, S>
702710
// ^ exit once table.size == 0
703711
let idx_end = bucket.index() + old_capacity;
704712

705-
while bucket.index() != idx_end {
706-
bucket = match bucket.peek() {
707-
Full(bucket) => {
708-
let h = *bucket.read().0;
709-
let (b, k, v) = bucket.take();
710-
insert_hashed_ordered(&mut destination, h, k, v);
711-
b.into_bucket()
712-
}
713-
Empty(b) => b.into_bucket()
714-
};
715-
bucket.next(); // wraps at old_capacity
716-
}
713+
if let Some(mut dest) = destination {
714+
while bucket.index() != idx_end {
715+
bucket = match bucket.peek() {
716+
Full(bucket) => {
717+
let h = *bucket.read().0;
718+
let (b, k, v) = bucket.take();
719+
insert_hashed_ordered(&mut dest, h, k, v);
720+
b.into_bucket()
721+
}
722+
Empty(b) => b.into_bucket()
723+
};
724+
bucket.next(); // wraps at old_capacity
725+
}
717726

718-
replace(bucket.into_table(), destination);
727+
replace(bucket.into_table(), dest);
728+
} else {
729+
while bucket.index() != idx_end {
730+
bucket = match bucket.peek() {
731+
Full(bucket) => {
732+
let h = *bucket.read().0;
733+
let (b, k, v) = bucket.take();
734+
// Resizing in-place.
735+
insert_hashed_ordered(b.into_bucket(), h, k, v)
736+
}
737+
Empty(b) => b.into_bucket()
738+
};
739+
bucket.next(); // wraps at old_capacity
740+
}
741+
}
719742
}
720743

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

src/libstd/collections/hash/table.rs

+55-6
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use ops::{Deref, DerefMut, Drop};
2424
use option::Option::{self, Some, None};
2525
use ptr::{self, Unique, PtrExt};
2626
use result::Result::{self, Ok, Err};
27-
use rt::heap::{allocate, deallocate, EMPTY};
27+
use rt::heap::{EMPTY, allocate, deallocate, reallocate_inplace};
2828
use collections::hash_state::HashState;
2929
use core::nonzero::NonZero;
3030

@@ -180,15 +180,15 @@ impl<K, V, M, S> Borrow<RawTable<K, V>> for Bucket<K, V, M, S>
180180
where M: Borrow<RawTable<K, V>>
181181
{
182182
fn borrow(&self) -> &RawTable<K, V> {
183-
bucket.table.0.borrow()
183+
self.table.0.borrow()
184184
}
185185
}
186186

187187
impl<K, V, M, S> BorrowMut<RawTable<K, V>> for Bucket<K, V, M, S>
188188
where M: Borrow<RawTable<K, V>>
189189
{
190190
fn borrow_mut(&mut self) -> &mut RawTable<K, V> {
191-
bucket.table.0.borrow_mut()
191+
self.table.0.borrow_mut()
192192
}
193193
}
194194

@@ -252,6 +252,14 @@ impl<K, V, M> Bucket<K, V, M> where M: Borrow<RawTable<K, V>> {
252252
Bucket::at_index(table, 0).map(|b| b.into_iter()).unwrap_or_else(|b| b.into_iter())
253253
}
254254

255+
/// Narrows down the range of iteration, which must be a power of 2.
256+
pub fn iter_to(mut self, limit: usize) -> Bucket<K, V, M> {
257+
assert!(limit <= self.table.capacity());
258+
assert!(limit.is_power_of_two());
259+
self.capacity = limit;
260+
self
261+
}
262+
255263
/// Reads a bucket at a given index, returning an enum indicating whether
256264
/// it's initialized or not. You need to match on this enum to get
257265
/// the appropriate types to call most of the other functions in
@@ -460,7 +468,7 @@ impl<K, V> RawTable<K, V> {
460468
RawTable {
461469
capacity: capacity,
462470
size: 0,
463-
middle: Unique((hashes as *mut (K, V)).offset(capacity as isize)),
471+
middle: Unique::new((hashes as *mut (K, V)).offset(capacity as isize)),
464472
}
465473
};
466474

@@ -495,6 +503,47 @@ impl<K, V> RawTable<K, V> {
495503
}
496504
}
497505

506+
pub fn grow_inplace(&mut self, capacity: uint) -> bool {
507+
assert!(capacity.is_power_of_two());
508+
assert!(capacity >= self.capacity);
509+
510+
if self.middle.ptr.is_null() {
511+
return false;
512+
}
513+
514+
let new_size = checked_size_generic::<K, V>(capacity);
515+
516+
unsafe {
517+
let ptr = self.middle.ptr.offset(-(self.capacity as isize)) as *mut u8;
518+
let is_inplace = reallocate_inplace(ptr,
519+
size_generic::<K, V>(self.capacity),
520+
new_size,
521+
align::<K, V>()) >= new_size;
522+
523+
if is_inplace {
524+
let cap_diff = (capacity - self.capacity) as isize;
525+
let hashes = self.middle.ptr.offset(cap_diff) as *mut Option<SafeHash>;
526+
// Copy the array of hashes. Maybe it's already in cache.
527+
if size_of::<(K, V)>() >= size_of::<Option<SafeHash>>() {
528+
// The regions of memory occupied by old and new hash arrays are disjoint.
529+
// before: [KVKVKVKV|h h h h ]
530+
// after: [KVKVKVKV|KVKVKVKV|h h h h h h h h ]
531+
ptr::copy_nonoverlapping(hashes, self.middle.ptr as *const _, self.capacity);
532+
} else {
533+
// before: [KVKVKVKV|h h |h h ]
534+
// after: [KVKVKVKV|KVKVKVKV|h h h h h h h h ]
535+
ptr::copy(hashes, self.middle.ptr as *const _, self.capacity);
536+
}
537+
zero_memory(hashes.offset(self.capacity as int), capacity - self.capacity);
538+
539+
self.middle = Unique::new(self.middle.ptr.offset(cap_diff));
540+
self.capacity = capacity;
541+
}
542+
543+
return is_inplace;
544+
}
545+
}
546+
498547
/// The hashtable's capacity, similar to a vector's.
499548
pub fn capacity(&self) -> usize {
500549
self.capacity
@@ -551,13 +600,13 @@ fn align<K, V>() -> usize {
551600
/// A newtyped RawBucket. Not copyable.
552601
pub struct RawFullBucket<K, V, M>(RawBucket<K, V>);
553602

554-
impl<'t, K, V, M: 't> RawFullBucket<K, V, M> where RawTable<K, V>: BorrowFrom<M> {
603+
impl<'t, K, V, M: 't> RawFullBucket<K, V, M> where M: Borrow<RawTable<K, V>> {
555604
pub fn into_refs(self) -> (&'t K, &'t V) {
556605
unsafe { (&(*self.0.kval).0, &(*self.0.kval).1) }
557606
}
558607
}
559608

560-
impl<'t, K, V, M: 't> RawFullBucket<K, V, M> where RawTable<K, V>: BorrowFromMut<M> {
609+
impl<'t, K, V, M: 't> RawFullBucket<K, V, M> where M: BorrowMut<RawTable<K, V>> {
561610
pub fn into_mut_refs(self) -> (&'t mut K, &'t mut V) {
562611
unsafe { (&mut (*self.0.kval).0, &mut (*self.0.kval).1) }
563612
}

0 commit comments

Comments
 (0)