Skip to content

Commit 7169114

Browse files
committed
Changed HashMap's internal layout.
1 parent 7be83bd commit 7169114

File tree

1 file changed

+111
-172
lines changed

1 file changed

+111
-172
lines changed

src/libstd/collections/hash/table.rs

Lines changed: 111 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,14 @@ use core::nonzero::NonZero;
6767
pub struct RawTable<K, V> {
6868
capacity: usize,
6969
size: usize,
70-
hashes: Unique<Option<SafeHash>>,
71-
// Because K/V do not appear directly in any of the types in the struct,
72-
// inform rustc that in fact instances of K and V are reachable from here.
73-
marker: marker::PhantomData<(K,V)>,
70+
// NB. The table will probably need manual impls of Send and Sync if this
71+
// field ever changes.
72+
middle: Unique<(K, V)>,
7473
}
7574

7675
struct RawBucket<K, V> {
7776
hash: *mut Option<SafeHash>,
78-
key: *mut K,
79-
val: *mut V,
80-
_marker: marker::PhantomData<(K,V)>,
77+
kval: *mut (K, V),
8178
}
8279

8380
impl<K, V> Copy for RawBucket<K, V> {}
@@ -191,9 +188,7 @@ impl<K, V> RawBucket<K, V> {
191188
unsafe fn offset(self, count: isize) -> RawBucket<K, V> {
192189
RawBucket {
193190
hash: self.hash.offset(count),
194-
key: self.key.offset(count),
195-
val: self.val.offset(count),
196-
_marker: marker::PhantomData,
191+
kval: self.kval.offset(count),
197192
}
198193
}
199194
}
@@ -221,6 +216,7 @@ impl<K, V> Put for RawTable<K, V> {}
221216
impl<'t, K, V> Put for &'t mut RawTable<K, V> {}
222217
impl<K, V, M: Put> Put for Bucket<K, V, M> {}
223218
impl<K, V, M: Put> Put for FullBucket<K, V, M> {}
219+
224220
// Buckets hold references to the table.
225221
impl<K, V, M, S> Bucket<K, V, M, S> {
226222
/// Borrow a reference to the table.
@@ -264,7 +260,6 @@ impl<K, V, M> Bucket<K, V, M> where M: Borrow<RawTable<K, V>> {
264260
capacity: capacity,
265261
table: table,
266262
};
267-
268263
if capacity == 0 {
269264
Err(bucket.unsafe_cast())
270265
} else {
@@ -369,8 +364,7 @@ impl<K, V, M> EmptyBucket<K, V, M> where M: Borrow<RawTable<K, V>>, M: Put {
369364
-> FullBucket<K, V, M> {
370365
unsafe {
371366
*self.raw.hash = Some(hash);
372-
ptr::write(self.0.raw.key, key);
373-
ptr::write(self.0.raw.val, value);
367+
ptr::write(self.raw.kval, (key, value));
374368
}
375369

376370
self.table.size += 1;
@@ -394,56 +388,49 @@ impl<'t, K, V, M: 't> FullBucket<K, V, M> where M: Borrow<RawTable<K, V>> {
394388

395389
/// Gets references to the key and value at a given index.
396390
pub fn read(&self) -> (&SafeHash, &K, &V) {
397-
unsafe {
398-
(&*(self.0.raw.hash as *mut SafeHash),
399-
&*self.0.raw.key,
400-
&*self.0.raw.val)
401-
}
402-
}
403-
}
404-
405-
impl<K, V, M> FullBucket<K, V, M> where M: Borrow<RawTable<K, V>> {
406-
/// Removes this bucket's key and value from the hashtable.
407-
///
408-
/// This works similarly to `put`, building an `EmptyBucket` out of the
409-
/// taken bucket.
410-
pub fn take(mut self) -> (EmptyBucket<K, V, M>, K, V) {
411-
self.0.table.size -= 1;
412-
413-
unsafe {
414-
*self.0.raw.hash = None;
415-
let k = ptr::read(self.0.raw.key);
416-
let v = ptr::read(self.0.raw.val);
417-
(Bucket(self.0), k, v)
418-
}
419-
}
420-
421-
/// Gets mutable references to the key and value at a given index.
422-
pub fn read_mut(&mut self) -> (&mut SafeHash, &mut K, &mut V) {
423-
unsafe {
424-
(&mut *(self.0.raw.hash as *mut SafeHash),
425-
&mut *self.0.raw.key,
426-
&mut *self.0.raw.val)
427-
}
391+
let (&ref h, &(ref k, ref v)) = unsafe {
392+
(&*(self.raw.hash as *mut SafeHash), &*self.raw.kval)
393+
};
394+
(h, k, v)
428395
}
429-
}
430396

431-
impl<'t, K, V, M: 't> FullBucket<K, V, M> where M: Borrow<RawTable<K, V>> {
432397
/// Exchange a bucket state for immutable references into the table.
433398
/// Because the underlying reference to the table is also consumed,
434399
/// no further changes to the structure of the table are possible;
435400
/// in exchange for this, the returned references have a longer lifetime
436401
/// than the references returned by `read()`.
437402
pub fn into_refs(self) -> (&'t K, &'t V) {
438-
self.0.raw.into_refs()
403+
unsafe { (&(*self.raw.kval).0, &(*self.raw.kval).1) }
439404
}
440405
}
441406

442-
impl<'t, K, V, M: 't> FullBucket<K, V, M> where M: Borrow<RawTable<K, V>> {
407+
impl<'t, K, V, M: 't> FullBucket<K, V, M> where M: BorrowMut<RawTable<K, V>> {
408+
/// Gets mutable references to the key and value at a given index.
409+
pub fn read_mut(&mut self) -> (&mut SafeHash, &mut K, &mut V) {
410+
let (&mut ref mut h, &mut (ref mut k, ref mut v)) = unsafe {
411+
(&mut *(self.raw.hash as *mut SafeHash), &mut *self.raw.kval)
412+
};
413+
(h, k, v)
414+
}
415+
443416
/// This works similarly to `into_refs`, exchanging a bucket state
444417
/// for mutable references into the table.
445418
pub fn into_mut_refs(self) -> (&'t mut K, &'t mut V) {
446-
self.0.raw.into_mut_refs()
419+
unsafe { (&mut (*self.raw.kval).0, &mut (*self.raw.kval).1) }
420+
}
421+
422+
/// Removes this bucket's key and value from the hashtable.
423+
///
424+
/// This works similarly to `put`, building an `EmptyBucket` out of the
425+
/// taken bucket.
426+
pub fn take(mut self) -> (EmptyBucket<K, V, M>, K, V) {
427+
self.table.size -= 1;
428+
429+
unsafe {
430+
*self.raw.hash = None;
431+
let (k, v) = ptr::read(self.raw.kval);
432+
(self.unsafe_cast(), k, v)
433+
}
447434
}
448435
}
449436

@@ -456,8 +443,7 @@ impl<K, V, M> GapThenFull<K, V, M> where M: Borrow<RawTable<K, V>> {
456443
pub fn shift(mut self) -> Option<GapThenFull<K, V, M>> {
457444
unsafe {
458445
*self.gap.raw.hash = mem::replace(&mut *self.full.raw.hash, None);
459-
copy_nonoverlapping_memory(self.gap.0.raw.key, self.full.0.raw.key, 1);
460-
copy_nonoverlapping_memory(self.gap.0.raw.val, self.full.0.raw.val, 1);
446+
copy_nonoverlapping_memory(self.gap.raw.kval, self.full.raw.kval, 1);
461447
}
462448

463449
let Bucket { raw: prev_raw, idx: prev_idx, .. } = self.full;
@@ -476,117 +462,34 @@ impl<K, V, M> GapThenFull<K, V, M> where M: Borrow<RawTable<K, V>> {
476462
}
477463
}
478464

479-
/// Rounds up to a multiple of a power of two. Returns the closest multiple
480-
/// of `target_alignment` that is higher or equal to `unrounded`.
481-
///
482-
/// # Panics
483-
///
484-
/// Panics if `target_alignment` is not a power of two.
485-
fn round_up_to_next(unrounded: usize, target_alignment: usize) -> usize {
486-
assert!(target_alignment.is_power_of_two());
487-
(unrounded + target_alignment - 1) & !(target_alignment - 1)
488-
}
489-
490-
#[test]
491-
fn test_rounding() {
492-
assert_eq!(round_up_to_next(0, 4), 0);
493-
assert_eq!(round_up_to_next(1, 4), 4);
494-
assert_eq!(round_up_to_next(2, 4), 4);
495-
assert_eq!(round_up_to_next(3, 4), 4);
496-
assert_eq!(round_up_to_next(4, 4), 4);
497-
assert_eq!(round_up_to_next(5, 4), 8);
498-
}
499-
500-
// Returns a tuple of (key_offset, val_offset),
501-
// from the start of a mallocated array.
502-
fn calculate_offsets(hashes_size: usize,
503-
keys_size: usize, keys_align: usize,
504-
vals_align: usize)
505-
-> (usize, usize) {
506-
let keys_offset = round_up_to_next(hashes_size, keys_align);
507-
let end_of_keys = keys_offset + keys_size;
508-
509-
let vals_offset = round_up_to_next(end_of_keys, vals_align);
510-
511-
(keys_offset, vals_offset)
512-
}
513-
514-
// Returns a tuple of (minimum required malloc alignment, hash_offset,
515-
// array_size), from the start of a mallocated array.
516-
fn calculate_allocation(hash_size: usize, hash_align: usize,
517-
keys_size: usize, keys_align: usize,
518-
vals_size: usize, vals_align: usize)
519-
-> (usize, usize, usize) {
520-
let hash_offset = 0;
521-
let (_, vals_offset) = calculate_offsets(hash_size,
522-
keys_size, keys_align,
523-
vals_align);
524-
let end_of_vals = vals_offset + vals_size;
525-
526-
let min_align = cmp::max(hash_align, cmp::max(keys_align, vals_align));
527-
528-
(min_align, hash_offset, end_of_vals)
529-
}
530-
531-
#[test]
532-
fn test_offset_calculation() {
533-
assert_eq!(calculate_allocation(128, 8, 15, 1, 4, 4), (8, 0, 148));
534-
assert_eq!(calculate_allocation(3, 1, 2, 1, 1, 1), (1, 0, 6));
535-
assert_eq!(calculate_allocation(6, 2, 12, 4, 24, 8), (8, 0, 48));
536-
assert_eq!(calculate_offsets(128, 15, 1, 4), (128, 144));
537-
assert_eq!(calculate_offsets(3, 2, 1, 1), (3, 5));
538-
assert_eq!(calculate_offsets(6, 12, 4, 8), (8, 24));
539-
}
540-
541465
impl<K, V> RawTable<K, V> {
542466
/// Does not initialize the buckets.
543-
unsafe fn new_uninitialized(capacity: uint) -> RawTable<K, V> {
544-
if capacity == 0 {
545-
return RawTable {
546-
size: 0,
547-
capacity: 0,
548-
hashes: Unique::new(EMPTY as *mut u64),
549-
marker: marker::PhantomData,
467+
pub fn new_uninitialized(capacity: uint) -> PartialRawTable<K, V> {
468+
unsafe {
469+
let table = if capacity == 0 {
470+
RawTable {
471+
size: 0,
472+
capacity: 0,
473+
middle: Unique::new(EMPTY as *mut _),
474+
}
475+
} else {
476+
let hashes = allocate(checked_size_generic::<K, V>(capacity), align::<K, V>());
477+
if hashes.is_null() { ::alloc::oom() }
478+
479+
RawTable {
480+
capacity: capacity,
481+
size: 0,
482+
middle: Unique((hashes as *mut (K, V)).offset(capacity as isize)),
483+
}
550484
};
551-
}
552485

553-
// No need for `checked_mul` before a more restrictive check performed
554-
// later in this method.
555-
let hashes_size = capacity * size_of::<Option<SafeHash>>();
556-
let keys_size = capacity * size_of::<K>();
557-
let vals_size = capacity * size_of::<V>();
558-
559-
// Allocating hashmaps is a little tricky. We need to allocate three
560-
// arrays, but since we know their sizes and alignments up front,
561-
// we just allocate a single array, and then have the subarrays
562-
// point into it.
563-
//
564-
// This is great in theory, but in practice getting the alignment
565-
// right is a little subtle. Therefore, calculating offsets has been
566-
// factored out into a different function.
567-
let (malloc_alignment, hash_offset, size) =
568-
calculate_allocation(
569-
hashes_size, min_align_of::<Option<SafeHash>>(),
570-
keys_size, min_align_of::< K >(),
571-
vals_size, min_align_of::< V >());
572-
573-
// One check for overflow that covers calculation and rounding of size.
574-
let size_of_bucket = size_of::<Option<SafeHash>>().checked_add(size_of::<K>()).unwrap()
575-
.checked_add(size_of::<V>()).unwrap();
576-
assert!(size >= capacity.checked_mul(size_of_bucket)
577-
.expect("capacity overflow"),
578-
"capacity overflow");
579-
580-
let buffer = allocate(size, malloc_alignment);
581-
if buffer.is_null() { ::alloc::oom() }
582-
583-
let hashes = buffer.offset(hash_offset as isize) as *mut Option<SafeHash>;
584-
585-
RawTable {
586-
capacity: capacity,
587-
size: 0,
588-
hashes: Unique::new(hashes),
589-
marker: marker::PhantomData,
486+
PartialRawTable {
487+
front: table.first_bucket_raw(),
488+
back: table.first_bucket_raw(),
489+
front_num: 0,
490+
back_num: capacity,
491+
table: table,
492+
}
590493
}
591494
}
592495

@@ -601,23 +504,12 @@ impl<K, V> RawTable<K, V> {
601504
if self.capacity() == 0 {
602505
RawBucket {
603506
hash: ptr::null_mut(),
604-
key: ptr::null_mut(),
605-
val: ptr::null_mut(),
507+
kval: ptr::null_mut(),
606508
}
607509
} else {
608-
let hashes_size = self.capacity * size_of::<Option<SafeHash>>();
609-
let keys_size = self.capacity * size_of::<K>();
610-
611-
let buffer = *self.hashes as *mut u8;
612-
let (keys_offset, vals_offset) = calculate_offsets(hashes_size,
613-
keys_size, min_align_of::<K>(),
614-
min_align_of::<V>());
615-
616510
RawBucket {
617-
hash: *self.middle as *mut Option<SafeHash>,
618-
key: buffer.offset(keys_offset as isize) as *mut K,
619-
val: buffer.offset(vals_offset as isize) as *mut V,
620-
_marker: marker::PhantomData,
511+
hash: self.middle.ptr as *mut Option<SafeHash>,
512+
kval: self.middle.ptr.offset(-(self.capacity as isize)),
621513
}
622514
}
623515
}
@@ -632,14 +524,61 @@ impl<K, V> RawTable<K, V> {
632524
pub fn size(&self) -> usize {
633525
self.size
634526
}
527+
}
635528

529+
/// Rounds up to a multiple of a power of two. Returns the closest multiple
530+
/// of `target_alignment` that is higher or equal to `unrounded`.
531+
///
532+
/// # Panics
533+
///
534+
/// Panics if `target_alignment` is not a power of two.
535+
fn round_up_to_next(unrounded: uint, target_alignment: uint) -> uint {
536+
assert!(target_alignment.is_power_of_two());
537+
(unrounded + target_alignment - 1) & !(target_alignment - 1)
636538
}
637539

540+
#[test]
541+
fn test_rounding() {
542+
assert_eq!(round_up_to_next(0, 4), 0);
543+
assert_eq!(round_up_to_next(1, 4), 4);
544+
assert_eq!(round_up_to_next(2, 4), 4);
545+
assert_eq!(round_up_to_next(3, 4), 4);
546+
assert_eq!(round_up_to_next(4, 4), 4);
547+
assert_eq!(round_up_to_next(5, 4), 8);
548+
assert_eq!(round_up_to_next(5, 8), 8);
549+
}
638550

551+
#[inline]
552+
fn size_generic<K, V>(capacity: usize) -> usize {
553+
let hash_align = min_align_of::<Option<SafeHash>>();
554+
round_up_to_next(size_of::<(K, V)>() * capacity, hash_align) + size_of::<SafeHash>() * capacity
555+
}
639556

557+
fn checked_size_generic<K, V>(capacity: usize) -> usize {
558+
let size = size_generic::<K, V>(capacity);
559+
let elem_size = size_of::<(K, V)>() + size_of::<SafeHash>();
560+
assert!(size >= capacity.checked_mul(elem_size).expect("capacity overflow"),
561+
"capacity overflow");
562+
size
563+
}
564+
565+
#[inline]
566+
fn align<K, V>() -> usize {
567+
cmp::max(mem::min_align_of::<(K, V)>(), mem::min_align_of::<u64>())
568+
}
569+
570+
/// A newtyped RawBucket. Not copyable.
571+
pub struct RawFullBucket<K, V, M>(RawBucket<K, V>);
572+
573+
impl<'t, K, V, M: 't> RawFullBucket<K, V, M> where RawTable<K, V>: BorrowFrom<M> {
574+
pub fn into_refs(self) -> (&'t K, &'t V) {
575+
unsafe { (&(*self.0.kval).0, &(*self.0.kval).1) }
640576
}
641577
}
642578

579+
impl<'t, K, V, M: 't> RawFullBucket<K, V, M> where RawTable<K, V>: BorrowFromMut<M> {
580+
pub fn into_mut_refs(self) -> (&'t mut K, &'t mut V) {
581+
unsafe { (&mut (*self.0.kval).0, &mut (*self.0.kval).1) }
643582
}
644583
}
645584

0 commit comments

Comments
 (0)