Skip to content

Commit aa094a4

Browse files
committed
Auto merge of #51163 - Amanieu:hashmap_layout, r=SimonSapin
Simplify HashMap layout calculation by using Layout `RawTable` uses a single allocation to hold both the array of hashes and the array of key/value pairs. This PR changes `RawTable` to use `Layout` when calculating the amount of memory to allocate instead of performing the calculation manually. r? @SimonSapin
2 parents 747e655 + c6bebf4 commit aa094a4

File tree

2 files changed

+21
-107
lines changed

2 files changed

+21
-107
lines changed

Diff for: src/libcore/alloc.rs

+8
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,14 @@ impl From<AllocErr> for CollectionAllocErr {
392392
}
393393
}
394394

395+
#[unstable(feature = "try_reserve", reason = "new API", issue="48043")]
396+
impl From<LayoutErr> for CollectionAllocErr {
397+
#[inline]
398+
fn from(_: LayoutErr) -> Self {
399+
CollectionAllocErr::CapacityOverflow
400+
}
401+
}
402+
395403
/// A memory allocator that can be registered to be the one backing `std::alloc::Global`
396404
/// though the `#[global_allocator]` attributes.
397405
pub unsafe trait GlobalAlloc {

Diff for: src/libstd/collections/hash/table.rs

+13-107
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
use alloc::{Global, Alloc, Layout, CollectionAllocErr, oom};
12-
use cmp;
11+
use alloc::{Global, Alloc, Layout, LayoutErr, CollectionAllocErr, oom};
1312
use hash::{BuildHasher, Hash, Hasher};
1413
use marker;
15-
use mem::{align_of, size_of, needs_drop};
14+
use mem::{size_of, needs_drop};
1615
use mem;
1716
use ops::{Deref, DerefMut};
1817
use ptr::{self, Unique, NonNull};
@@ -651,64 +650,12 @@ impl<K, V, M> GapThenFull<K, V, M>
651650
}
652651
}
653652

654-
655-
/// Rounds up to a multiple of a power of two. Returns the closest multiple
656-
/// of `target_alignment` that is higher or equal to `unrounded`.
657-
///
658-
/// # Panics
659-
///
660-
/// Panics if `target_alignment` is not a power of two.
661-
#[inline]
662-
fn round_up_to_next(unrounded: usize, target_alignment: usize) -> usize {
663-
assert!(target_alignment.is_power_of_two());
664-
(unrounded + target_alignment - 1) & !(target_alignment - 1)
665-
}
666-
667-
#[test]
668-
fn test_rounding() {
669-
assert_eq!(round_up_to_next(0, 4), 0);
670-
assert_eq!(round_up_to_next(1, 4), 4);
671-
assert_eq!(round_up_to_next(2, 4), 4);
672-
assert_eq!(round_up_to_next(3, 4), 4);
673-
assert_eq!(round_up_to_next(4, 4), 4);
674-
assert_eq!(round_up_to_next(5, 4), 8);
675-
}
676-
677-
// Returns a tuple of (pairs_offset, end_of_pairs_offset),
678-
// from the start of a mallocated array.
679-
#[inline]
680-
fn calculate_offsets(hashes_size: usize,
681-
pairs_size: usize,
682-
pairs_align: usize)
683-
-> (usize, usize, bool) {
684-
let pairs_offset = round_up_to_next(hashes_size, pairs_align);
685-
let (end_of_pairs, oflo) = pairs_offset.overflowing_add(pairs_size);
686-
687-
(pairs_offset, end_of_pairs, oflo)
688-
}
689-
690-
// Returns a tuple of (minimum required malloc alignment,
691-
// array_size), from the start of a mallocated array.
692-
fn calculate_allocation(hash_size: usize,
693-
hash_align: usize,
694-
pairs_size: usize,
695-
pairs_align: usize)
696-
-> (usize, usize, bool) {
697-
let (_, end_of_pairs, oflo) = calculate_offsets(hash_size, pairs_size, pairs_align);
698-
699-
let align = cmp::max(hash_align, pairs_align);
700-
701-
(align, end_of_pairs, oflo)
702-
}
703-
704-
#[test]
705-
fn test_offset_calculation() {
706-
assert_eq!(calculate_allocation(128, 8, 16, 8), (8, 144, false));
707-
assert_eq!(calculate_allocation(3, 1, 2, 1), (1, 5, false));
708-
assert_eq!(calculate_allocation(6, 2, 12, 4), (4, 20, false));
709-
assert_eq!(calculate_offsets(128, 15, 4), (128, 143, false));
710-
assert_eq!(calculate_offsets(3, 2, 4), (4, 6, false));
711-
assert_eq!(calculate_offsets(6, 12, 4), (8, 20, false));
653+
// Returns a Layout which describes the allocation required for a hash table,
654+
// and the offset of the array of (key, value) pairs in the allocation.
655+
fn calculate_layout<K, V>(capacity: usize) -> Result<(Layout, usize), LayoutErr> {
656+
let hashes = Layout::array::<HashUint>(capacity)?;
657+
let pairs = Layout::array::<(K, V)>(capacity)?;
658+
hashes.extend(pairs)
712659
}
713660

714661
pub(crate) enum Fallibility {
@@ -735,37 +682,11 @@ impl<K, V> RawTable<K, V> {
735682
});
736683
}
737684

738-
// No need for `checked_mul` before a more restrictive check performed
739-
// later in this method.
740-
let hashes_size = capacity.wrapping_mul(size_of::<HashUint>());
741-
let pairs_size = capacity.wrapping_mul(size_of::<(K, V)>());
742-
743685
// Allocating hashmaps is a little tricky. We need to allocate two
744686
// arrays, but since we know their sizes and alignments up front,
745687
// we just allocate a single array, and then have the subarrays
746688
// point into it.
747-
//
748-
// This is great in theory, but in practice getting the alignment
749-
// right is a little subtle. Therefore, calculating offsets has been
750-
// factored out into a different function.
751-
let (alignment, size, oflo) = calculate_allocation(hashes_size,
752-
align_of::<HashUint>(),
753-
pairs_size,
754-
align_of::<(K, V)>());
755-
if oflo {
756-
return Err(CollectionAllocErr::CapacityOverflow);
757-
}
758-
759-
// One check for overflow that covers calculation and rounding of size.
760-
let size_of_bucket = size_of::<HashUint>().checked_add(size_of::<(K, V)>())
761-
.ok_or(CollectionAllocErr::CapacityOverflow)?;
762-
let capacity_mul_size_of_bucket = capacity.checked_mul(size_of_bucket);
763-
if capacity_mul_size_of_bucket.is_none() || size < capacity_mul_size_of_bucket.unwrap() {
764-
return Err(CollectionAllocErr::CapacityOverflow);
765-
}
766-
767-
let layout = Layout::from_size_align(size, alignment)
768-
.map_err(|_| CollectionAllocErr::CapacityOverflow)?;
689+
let (layout, _) = calculate_layout::<K, V>(capacity)?;
769690
let buffer = Global.alloc(layout).map_err(|e| match fallibility {
770691
Infallible => oom(layout),
771692
Fallible => e,
@@ -790,18 +711,12 @@ impl<K, V> RawTable<K, V> {
790711
}
791712

792713
fn raw_bucket_at(&self, index: usize) -> RawBucket<K, V> {
793-
let hashes_size = self.capacity() * size_of::<HashUint>();
794-
let pairs_size = self.capacity() * size_of::<(K, V)>();
795-
796-
let (pairs_offset, _, oflo) =
797-
calculate_offsets(hashes_size, pairs_size, align_of::<(K, V)>());
798-
debug_assert!(!oflo, "capacity overflow");
799-
714+
let (_, pairs_offset) = calculate_layout::<K, V>(self.capacity()).unwrap();
800715
let buffer = self.hashes.ptr() as *mut u8;
801716
unsafe {
802717
RawBucket {
803718
hash_start: buffer as *mut HashUint,
804-
pair_start: buffer.offset(pairs_offset as isize) as *const (K, V),
719+
pair_start: buffer.add(pairs_offset) as *const (K, V),
805720
idx: index,
806721
_marker: marker::PhantomData,
807722
}
@@ -1194,18 +1109,9 @@ unsafe impl<#[may_dangle] K, #[may_dangle] V> Drop for RawTable<K, V> {
11941109
}
11951110
}
11961111

1197-
let hashes_size = self.capacity() * size_of::<HashUint>();
1198-
let pairs_size = self.capacity() * size_of::<(K, V)>();
1199-
let (align, size, oflo) = calculate_allocation(hashes_size,
1200-
align_of::<HashUint>(),
1201-
pairs_size,
1202-
align_of::<(K, V)>());
1203-
1204-
debug_assert!(!oflo, "should be impossible");
1205-
1112+
let (layout, _) = calculate_layout::<K, V>(self.capacity()).unwrap();
12061113
unsafe {
1207-
Global.dealloc(NonNull::new_unchecked(self.hashes.ptr()).as_opaque(),
1208-
Layout::from_size_align(size, align).unwrap());
1114+
Global.dealloc(NonNull::new_unchecked(self.hashes.ptr()).as_opaque(), layout);
12091115
// Remember how everything was allocated out of one buffer
12101116
// during initialization? We only need one call to free here.
12111117
}

0 commit comments

Comments
 (0)