Skip to content

Commit 8a3343b

Browse files
committed
Changed HashMap's internal layout.
1 parent 08c1108 commit 8a3343b

File tree

1 file changed

+111
-172
lines changed

1 file changed

+111
-172
lines changed

src/libstd/collections/hash/table.rs

+111-172
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> {}
@@ -172,9 +169,7 @@ impl<K, V> RawBucket<K, V> {
172169
unsafe fn offset(self, count: isize) -> RawBucket<K, V> {
173170
RawBucket {
174171
hash: self.hash.offset(count),
175-
key: self.key.offset(count),
176-
val: self.val.offset(count),
177-
_marker: marker::PhantomData,
172+
kval: self.kval.offset(count),
178173
}
179174
}
180175
}
@@ -202,6 +197,7 @@ impl<K, V> Put for RawTable<K, V> {}
202197
impl<'t, K, V> Put for &'t mut RawTable<K, V> {}
203198
impl<K, V, M: Put> Put for Bucket<K, V, M> {}
204199
impl<K, V, M: Put> Put for FullBucket<K, V, M> {}
200+
205201
// Buckets hold references to the table.
206202
impl<K, V, M, S> Bucket<K, V, M, S> {
207203
/// Borrow a reference to the table.
@@ -245,7 +241,6 @@ impl<K, V, M> Bucket<K, V, M> where M: Borrow<RawTable<K, V>> {
245241
capacity: capacity,
246242
table: table,
247243
};
248-
249244
if capacity == 0 {
250245
Err(bucket.unsafe_cast())
251246
} else {
@@ -350,8 +345,7 @@ impl<K, V, M> EmptyBucket<K, V, M> where M: Borrow<RawTable<K, V>>, M: Put {
350345
-> FullBucket<K, V, M> {
351346
unsafe {
352347
*self.raw.hash = Some(hash);
353-
ptr::write(self.0.raw.key, key);
354-
ptr::write(self.0.raw.val, value);
348+
ptr::write(self.raw.kval, (key, value));
355349
}
356350

357351
self.table.size += 1;
@@ -375,56 +369,49 @@ impl<'t, K, V, M: 't> FullBucket<K, V, M> where M: Borrow<RawTable<K, V>> {
375369

376370
/// Gets references to the key and value at a given index.
377371
pub fn read(&self) -> (&SafeHash, &K, &V) {
378-
unsafe {
379-
(&*(self.0.raw.hash as *mut SafeHash),
380-
&*self.0.raw.key,
381-
&*self.0.raw.val)
382-
}
383-
}
384-
}
385-
386-
impl<K, V, M> FullBucket<K, V, M> where M: Borrow<RawTable<K, V>> {
387-
/// Removes this bucket's key and value from the hashtable.
388-
///
389-
/// This works similarly to `put`, building an `EmptyBucket` out of the
390-
/// taken bucket.
391-
pub fn take(mut self) -> (EmptyBucket<K, V, M>, K, V) {
392-
self.0.table.size -= 1;
393-
394-
unsafe {
395-
*self.0.raw.hash = None;
396-
let k = ptr::read(self.0.raw.key);
397-
let v = ptr::read(self.0.raw.val);
398-
(Bucket(self.0), k, v)
399-
}
400-
}
401-
402-
/// Gets mutable references to the key and value at a given index.
403-
pub fn read_mut(&mut self) -> (&mut SafeHash, &mut K, &mut V) {
404-
unsafe {
405-
(&mut *(self.0.raw.hash as *mut SafeHash),
406-
&mut *self.0.raw.key,
407-
&mut *self.0.raw.val)
408-
}
372+
let (&ref h, &(ref k, ref v)) = unsafe {
373+
(&*(self.raw.hash as *mut SafeHash), &*self.raw.kval)
374+
};
375+
(h, k, v)
409376
}
410-
}
411377

412-
impl<'t, K, V, M: 't> FullBucket<K, V, M> where M: Borrow<RawTable<K, V>> {
413378
/// Exchange a bucket state for immutable references into the table.
414379
/// Because the underlying reference to the table is also consumed,
415380
/// no further changes to the structure of the table are possible;
416381
/// in exchange for this, the returned references have a longer lifetime
417382
/// than the references returned by `read()`.
418383
pub fn into_refs(self) -> (&'t K, &'t V) {
419-
self.0.raw.into_refs()
384+
unsafe { (&(*self.raw.kval).0, &(*self.raw.kval).1) }
420385
}
421386
}
422387

423-
impl<'t, K, V, M: 't> FullBucket<K, V, M> where M: Borrow<RawTable<K, V>> {
388+
impl<'t, K, V, M: 't> FullBucket<K, V, M> where M: BorrowMut<RawTable<K, V>> {
389+
/// Gets mutable references to the key and value at a given index.
390+
pub fn read_mut(&mut self) -> (&mut SafeHash, &mut K, &mut V) {
391+
let (&mut ref mut h, &mut (ref mut k, ref mut v)) = unsafe {
392+
(&mut *(self.raw.hash as *mut SafeHash), &mut *self.raw.kval)
393+
};
394+
(h, k, v)
395+
}
396+
424397
/// This works similarly to `into_refs`, exchanging a bucket state
425398
/// for mutable references into the table.
426399
pub fn into_mut_refs(self) -> (&'t mut K, &'t mut V) {
427-
self.0.raw.into_mut_refs()
400+
unsafe { (&mut (*self.raw.kval).0, &mut (*self.raw.kval).1) }
401+
}
402+
403+
/// Removes this bucket's key and value from the hashtable.
404+
///
405+
/// This works similarly to `put`, building an `EmptyBucket` out of the
406+
/// taken bucket.
407+
pub fn take(mut self) -> (EmptyBucket<K, V, M>, K, V) {
408+
self.table.size -= 1;
409+
410+
unsafe {
411+
*self.raw.hash = None;
412+
let (k, v) = ptr::read(self.raw.kval);
413+
(self.unsafe_cast(), k, v)
414+
}
428415
}
429416
}
430417

@@ -437,8 +424,7 @@ impl<K, V, M> GapThenFull<K, V, M> where M: Borrow<RawTable<K, V>> {
437424
pub fn shift(mut self) -> Option<GapThenFull<K, V, M>> {
438425
unsafe {
439426
*self.gap.raw.hash = mem::replace(&mut *self.full.raw.hash, None);
440-
ptr::copy_nonoverlapping(self.gap.0.raw.key, self.full.0.raw.key, 1);
441-
ptr::copy_nonoverlapping(self.gap.0.raw.val, self.full.0.raw.val, 1);
427+
ptr::copy_nonoverlapping(self.gap.raw.kval, self.full.raw.kval, 1);
442428
}
443429

444430
let Bucket { raw: prev_raw, idx: prev_idx, .. } = self.full;
@@ -457,117 +443,34 @@ impl<K, V, M> GapThenFull<K, V, M> where M: Borrow<RawTable<K, V>> {
457443
}
458444
}
459445

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

534-
// No need for `checked_mul` before a more restrictive check performed
535-
// later in this method.
536-
let hashes_size = capacity * size_of::<Option<SafeHash>>();
537-
let keys_size = capacity * size_of::<K>();
538-
let vals_size = capacity * size_of::<V>();
539-
540-
// Allocating hashmaps is a little tricky. We need to allocate three
541-
// arrays, but since we know their sizes and alignments up front,
542-
// we just allocate a single array, and then have the subarrays
543-
// point into it.
544-
//
545-
// This is great in theory, but in practice getting the alignment
546-
// right is a little subtle. Therefore, calculating offsets has been
547-
// factored out into a different function.
548-
let (malloc_alignment, hash_offset, size) =
549-
calculate_allocation(
550-
hashes_size, min_align_of::<Option<SafeHash>>(),
551-
keys_size, min_align_of::< K >(),
552-
vals_size, min_align_of::< V >());
553-
554-
// One check for overflow that covers calculation and rounding of size.
555-
let size_of_bucket = size_of::<Option<SafeHash>>().checked_add(size_of::<K>()).unwrap()
556-
.checked_add(size_of::<V>()).unwrap();
557-
assert!(size >= capacity.checked_mul(size_of_bucket)
558-
.expect("capacity overflow"),
559-
"capacity overflow");
560-
561-
let buffer = allocate(size, malloc_alignment);
562-
if buffer.is_null() { ::alloc::oom() }
563-
564-
let hashes = buffer.offset(hash_offset as isize) as *mut Option<SafeHash>;
565-
566-
RawTable {
567-
capacity: capacity,
568-
size: 0,
569-
hashes: Unique::new(hashes),
570-
marker: marker::PhantomData,
467+
PartialRawTable {
468+
front: table.first_bucket_raw(),
469+
back: table.first_bucket_raw(),
470+
front_num: 0,
471+
back_num: capacity,
472+
table: table,
473+
}
571474
}
572475
}
573476

@@ -582,23 +485,12 @@ impl<K, V> RawTable<K, V> {
582485
if self.capacity() == 0 {
583486
RawBucket {
584487
hash: ptr::null_mut(),
585-
key: ptr::null_mut(),
586-
val: ptr::null_mut(),
488+
kval: ptr::null_mut(),
587489
}
588490
} else {
589-
let hashes_size = self.capacity * size_of::<Option<SafeHash>>();
590-
let keys_size = self.capacity * size_of::<K>();
591-
592-
let buffer = *self.hashes as *mut u8;
593-
let (keys_offset, vals_offset) = calculate_offsets(hashes_size,
594-
keys_size, min_align_of::<K>(),
595-
min_align_of::<V>());
596-
597491
RawBucket {
598-
hash: *self.middle as *mut Option<SafeHash>,
599-
key: buffer.offset(keys_offset as isize) as *mut K,
600-
val: buffer.offset(vals_offset as isize) as *mut V,
601-
_marker: marker::PhantomData,
492+
hash: self.middle.ptr as *mut Option<SafeHash>,
493+
kval: self.middle.ptr.offset(-(self.capacity as isize)),
602494
}
603495
}
604496
}
@@ -613,14 +505,61 @@ impl<K, V> RawTable<K, V> {
613505
pub fn size(&self) -> usize {
614506
self.size
615507
}
508+
}
616509

510+
/// Rounds up to a multiple of a power of two. Returns the closest multiple
511+
/// of `target_alignment` that is higher or equal to `unrounded`.
512+
///
513+
/// # Panics
514+
///
515+
/// Panics if `target_alignment` is not a power of two.
516+
fn round_up_to_next(unrounded: uint, target_alignment: uint) -> uint {
517+
assert!(target_alignment.is_power_of_two());
518+
(unrounded + target_alignment - 1) & !(target_alignment - 1)
617519
}
618520

521+
#[test]
522+
fn test_rounding() {
523+
assert_eq!(round_up_to_next(0, 4), 0);
524+
assert_eq!(round_up_to_next(1, 4), 4);
525+
assert_eq!(round_up_to_next(2, 4), 4);
526+
assert_eq!(round_up_to_next(3, 4), 4);
527+
assert_eq!(round_up_to_next(4, 4), 4);
528+
assert_eq!(round_up_to_next(5, 4), 8);
529+
assert_eq!(round_up_to_next(5, 8), 8);
530+
}
619531

532+
#[inline]
533+
fn size_generic<K, V>(capacity: usize) -> usize {
534+
let hash_align = min_align_of::<Option<SafeHash>>();
535+
round_up_to_next(size_of::<(K, V)>() * capacity, hash_align) + size_of::<SafeHash>() * capacity
536+
}
620537

538+
fn checked_size_generic<K, V>(capacity: usize) -> usize {
539+
let size = size_generic::<K, V>(capacity);
540+
let elem_size = size_of::<(K, V)>() + size_of::<SafeHash>();
541+
assert!(size >= capacity.checked_mul(elem_size).expect("capacity overflow"),
542+
"capacity overflow");
543+
size
544+
}
545+
546+
#[inline]
547+
fn align<K, V>() -> usize {
548+
cmp::max(mem::min_align_of::<(K, V)>(), mem::min_align_of::<u64>())
549+
}
550+
551+
/// A newtyped RawBucket. Not copyable.
552+
pub struct RawFullBucket<K, V, M>(RawBucket<K, V>);
553+
554+
impl<'t, K, V, M: 't> RawFullBucket<K, V, M> where RawTable<K, V>: BorrowFrom<M> {
555+
pub fn into_refs(self) -> (&'t K, &'t V) {
556+
unsafe { (&(*self.0.kval).0, &(*self.0.kval).1) }
621557
}
622558
}
623559

560+
impl<'t, K, V, M: 't> RawFullBucket<K, V, M> where RawTable<K, V>: BorrowFromMut<M> {
561+
pub fn into_mut_refs(self) -> (&'t mut K, &'t mut V) {
562+
unsafe { (&mut (*self.0.kval).0, &mut (*self.0.kval).1) }
624563
}
625564
}
626565

0 commit comments

Comments
 (0)