Skip to content

Commit 2c22ae4

Browse files
committed
auto merge of #13614 : cgaebel/rust/master, r=brson
We previously allocated 3x for every HashMap creation and resize. This patch reduces it to 1x.
2 parents c7553ea + 9f45484 commit 2c22ae4

File tree

2 files changed

+72
-30
lines changed

2 files changed

+72
-30
lines changed

src/libcollections/hashmap.rs

+66-30
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,17 @@ mod table {
3333
extern crate libc;
3434

3535
use std::clone::Clone;
36+
use std::cmp;
3637
use std::cmp::Eq;
3738
use std::hash::{Hash, Hasher};
3839
use std::kinds::marker;
39-
use std::num::CheckedMul;
40+
use std::num::{CheckedMul, is_power_of_two};
4041
use std::option::{Option, Some, None};
4142
use std::prelude::Drop;
4243
use std::ptr;
4344
use std::ptr::RawPtr;
4445
use std::rt::global_heap;
45-
use std::intrinsics::{size_of, transmute, move_val_init};
46+
use std::intrinsics::{size_of, min_align_of, transmute, move_val_init};
4647
use std::iter::{Iterator, range_step_inclusive};
4748

4849
static EMPTY_BUCKET: u64 = 0u64;
@@ -163,6 +164,42 @@ mod table {
163164
}
164165
}
165166

167+
fn round_up_to_next(unrounded: uint, target_alignment: uint) -> uint {
168+
assert!(is_power_of_two(target_alignment));
169+
(unrounded + target_alignment - 1) & !(target_alignment - 1)
170+
}
171+
172+
#[test]
173+
fn test_rounding() {
174+
assert!(round_up_to_next(0, 4) == 0);
175+
assert!(round_up_to_next(1, 4) == 4);
176+
assert!(round_up_to_next(2, 4) == 4);
177+
assert!(round_up_to_next(3, 4) == 4);
178+
assert!(round_up_to_next(4, 4) == 4);
179+
assert!(round_up_to_next(5, 4) == 8);
180+
}
181+
182+
// Returns a tuple of (minimum required malloc alignment, hash_offset,
183+
// key_offset, val_offset, array_size), from the start of a mallocated array.
184+
fn calculate_offsets(
185+
hash_size: uint, hash_align: uint,
186+
keys_size: uint, keys_align: uint,
187+
vals_size: uint, vals_align: uint) -> (uint, uint, uint, uint, uint) {
188+
189+
let hash_offset = 0;
190+
let end_of_hashes = hash_offset + hash_size;
191+
192+
let keys_offset = round_up_to_next(end_of_hashes, keys_align);
193+
let end_of_keys = keys_offset + keys_size;
194+
195+
let vals_offset = round_up_to_next(end_of_keys, vals_align);
196+
let end_of_vals = vals_offset + vals_size;
197+
198+
let min_align = cmp::max(hash_align, cmp::max(keys_align, vals_align));
199+
200+
(min_align, hash_offset, keys_offset, vals_offset, end_of_vals)
201+
}
202+
166203
impl<K, V> RawTable<K, V> {
167204

168205
/// Does not initialize the buckets. The caller should ensure they,
@@ -175,32 +212,31 @@ mod table {
175212
let vals_size =
176213
capacity.checked_mul(&size_of::< V >()).expect("capacity overflow");
177214

178-
/*
179-
The following code was my first pass at making RawTable only
180-
allocate a single buffer, since that's all it needs. There's
181-
no logical reason for this to require three calls to malloc.
182-
183-
However, I'm not convinced the code below is correct. If you
184-
want to take a stab at it, please do! The alignment is
185-
especially tricky to get right, especially if you need more
186-
alignment than malloc guarantees.
187-
188-
let hashes_offset = 0;
189-
let keys_offset = align_size(hashes_offset + hashes_size, keys_align);
190-
let vals_offset = align_size(keys_offset + keys_size, vals_align);
191-
let end = vals_offset + vals_size;
192-
193-
let buffer = global_heap::malloc_raw(end);
194-
195-
let hashes = buffer.offset(hashes_offset) as *mut u64;
196-
let keys = buffer.offset(keys_offset) as *mut K;
197-
let vals = buffer.offset(vals_offset) as *mut V;
198-
199-
*/
200-
201-
let hashes = global_heap::malloc_raw(hashes_size) as *mut u64;
202-
let keys = global_heap::malloc_raw(keys_size) as *mut K;
203-
let vals = global_heap::malloc_raw(vals_size) as *mut V;
215+
// Allocating hashmaps is a little tricky. We need to allocate three
216+
// arrays here, but since we know their sizes and alignments up front,
217+
// we could theoretically allocate only a single array, and then have
218+
// the subarrays just point into it.
219+
//
220+
// This is great in theory, but in practice getting the alignment
221+
// right is a little subtle. Therefore, calculating offsets has been
222+
// factored out into a different function.
223+
let (malloc_alignment, hash_offset, keys_offset, vals_offset, size) =
224+
calculate_offsets(
225+
hashes_size, min_align_of::<u64>(),
226+
keys_size, min_align_of::< K >(),
227+
vals_size, min_align_of::< V >());
228+
229+
let buffer = global_heap::malloc_raw(size) as *mut u8;
230+
231+
// FIXME #13094: If malloc was not at as aligned as we expected,
232+
// our offset calculations are just plain wrong. We could support
233+
// any alignment if we switched from `malloc` to `posix_memalign`.
234+
assert!(round_up_to_next(buffer as uint, malloc_alignment)
235+
== (buffer as uint));
236+
237+
let hashes = buffer.offset(hash_offset as int) as *mut u64;
238+
let keys = buffer.offset(keys_offset as int) as *mut K;
239+
let vals = buffer.offset(vals_offset as int) as *mut V;
204240

205241
RawTable {
206242
capacity: capacity,
@@ -513,9 +549,9 @@ mod table {
513549
assert!(self.size == 0);
514550

515551
unsafe {
516-
libc::free(self.vals as *mut libc::c_void);
517-
libc::free(self.keys as *mut libc::c_void);
518552
libc::free(self.hashes as *mut libc::c_void);
553+
// Remember how everything was allocated out of one buffer
554+
// during initialization? We only need one call to free here.
519555
}
520556
}
521557
}

src/libstd/num/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,12 @@ pub fn next_power_of_two<T: Unsigned + Int>(n: T) -> T {
311311
tmp + one()
312312
}
313313

314+
// Returns `true` iff `n == 2^k` for some k.
315+
#[inline]
316+
pub fn is_power_of_two<T: Unsigned + Int>(n: T) -> bool {
317+
(n - one()) & n == zero()
318+
}
319+
314320
/// Returns the smallest power of 2 greater than or equal to `n`. If the next
315321
/// power of two is greater than the type's maximum value, `None` is returned,
316322
/// otherwise the power of 2 is wrapped in `Some`.

0 commit comments

Comments
 (0)