Skip to content

Commit 352ff77

Browse files
committed
alloc: make RawVec round up allocation sizes to next power-of-two
The system allocator may give more memory than requested, usually rounding up to the next power-of-two. This PR uses a similar logic inside RawVec to be able to make use of this excess memory. NB: this is *not* used for `with_capacity` and `shrink_to_fit` pending discussion in #29931
1 parent d41e599 commit 352ff77

File tree

4 files changed

+44
-19
lines changed

4 files changed

+44
-19
lines changed

src/liballoc/raw_vec.rs

+33-13
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ impl<T> RawVec<T> {
9999
heap::EMPTY as *mut u8
100100
} else {
101101
let align = mem::align_of::<T>();
102+
// FIXME: we do not round up the capacity here, pending the
103+
// discussion in #29931
102104
let ptr = heap::allocate(alloc_size, align);
103105
if ptr.is_null() {
104106
oom()
@@ -215,14 +217,13 @@ impl<T> RawVec<T> {
215217
} else {
216218
4
217219
};
218-
let ptr = heap::allocate(new_cap * elem_size, align);
220+
let (alloc_size, new_cap) = round_up_alloc_size::<T>(new_cap);
221+
let ptr = heap::allocate(alloc_size, align);
219222
(new_cap, ptr)
220223
} else {
221224
// Since we guarantee that we never allocate more than isize::MAX bytes,
222225
// `elem_size * self.cap <= isize::MAX` as a precondition, so this can't overflow
223-
let new_cap = 2 * self.cap;
224-
let new_alloc_size = new_cap * elem_size;
225-
alloc_guard(new_alloc_size);
226+
let (new_alloc_size, new_cap) = round_up_alloc_size::<T>(2 * self.cap);
226227
let ptr = heap::reallocate(self.ptr() as *mut _,
227228
self.cap * elem_size,
228229
new_alloc_size,
@@ -278,8 +279,7 @@ impl<T> RawVec<T> {
278279

279280
// Nothing we can really do about these checks :(
280281
let new_cap = used_cap.checked_add(needed_extra_cap).expect("capacity overflow");
281-
let new_alloc_size = new_cap.checked_mul(elem_size).expect("capacity overflow");
282-
alloc_guard(new_alloc_size);
282+
let (new_alloc_size, new_cap) = round_up_alloc_size::<T>(new_cap);
283283

284284
let ptr = if self.cap == 0 {
285285
heap::allocate(new_alloc_size, align)
@@ -370,9 +370,7 @@ impl<T> RawVec<T> {
370370
// `double_cap` guarantees exponential growth.
371371
let new_cap = cmp::max(double_cap, required_cap);
372372

373-
let new_alloc_size = new_cap.checked_mul(elem_size).expect("capacity overflow");
374-
// FIXME: may crash and burn on over-reserve
375-
alloc_guard(new_alloc_size);
373+
let (new_alloc_size, new_cap) = round_up_alloc_size::<T>(new_cap);
376374

377375
let ptr = if self.cap == 0 {
378376
heap::allocate(new_alloc_size, align)
@@ -422,6 +420,9 @@ impl<T> RawVec<T> {
422420
unsafe {
423421
// Overflow check is unnecessary as the vector is already at
424422
// least this large.
423+
// FIXME: pending the discussion in #29931, we do not round up
424+
// the capacity here, since it might break assumptions for
425+
// example in Vec::into_boxed_slice
425426
let ptr = heap::reallocate(self.ptr() as *mut _,
426427
self.cap * elem_size,
427428
amount * elem_size,
@@ -475,7 +476,24 @@ impl<T> Drop for RawVec<T> {
475476
}
476477
}
477478

479+
// The system allocator may actually give us more memory than we requested.
480+
// The allocator usually gives a power-of-two, so instead of asking the
481+
// allocator via `heap::usable_size` which would incur some overhead, we rather
482+
// round up the request ourselves to the nearest power-of-two
483+
484+
#[inline]
485+
fn round_up_alloc_size<T>(cap: usize) -> (usize, usize) {
486+
let elem_size = mem::size_of::<T>();
487+
488+
let alloc_size = cap.checked_mul(elem_size)
489+
.and_then(usize::checked_next_power_of_two).expect("capacity overflow");
490+
alloc_guard(alloc_size);
491+
492+
let cap = alloc_size / elem_size;
493+
let alloc_size = cap * elem_size;
478494

495+
(alloc_size, cap)
496+
}
479497

480498
// We need to guarantee the following:
481499
// * We don't ever allocate `> isize::MAX` byte-size objects
@@ -501,27 +519,29 @@ mod tests {
501519

502520
#[test]
503521
fn reserve_does_not_overallocate() {
522+
// NB: when rounding up allocation sizes, the cap is not exact
523+
// but uses the whole memory allocated by the system allocator
504524
{
505525
let mut v: RawVec<u32> = RawVec::new();
506526
// First `reserve` allocates like `reserve_exact`
507527
v.reserve(0, 9);
508-
assert_eq!(9, v.cap());
528+
assert!(v.cap() >= 9 && v.cap() <= 9 * 2);
509529
}
510530

511531
{
512532
let mut v: RawVec<u32> = RawVec::new();
513533
v.reserve(0, 7);
514-
assert_eq!(7, v.cap());
534+
assert!(v.cap() >= 7 && v.cap() <= 7 * 2);
515535
// 97 if more than double of 7, so `reserve` should work
516536
// like `reserve_exact`.
517537
v.reserve(7, 90);
518-
assert_eq!(97, v.cap());
538+
assert!(v.cap() >= 97 && v.cap() <= 97 * 2);
519539
}
520540

521541
{
522542
let mut v: RawVec<u32> = RawVec::new();
523543
v.reserve(0, 12);
524-
assert_eq!(12, v.cap());
544+
assert!(v.cap() >= 12 && v.cap() <= 12 * 2);
525545
v.reserve(12, 3);
526546
// 3 is less than half of 12, so `reserve` must grow
527547
// exponentially. At the time of writing this test grow

src/libcollections/string.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ use boxed::Box;
148148
/// let len = story.len();
149149
/// let capacity = story.capacity();
150150
///
151-
/// // story has thirteen bytes
151+
/// // story has nineteen bytes
152152
/// assert_eq!(19, len);
153153
///
154154
/// // Now that we have our parts, we throw the story away.
@@ -191,6 +191,9 @@ use boxed::Box;
191191
/// 40
192192
/// ```
193193
///
194+
/// Please note that the actual output may be higher than stated as the
195+
/// allocator may reserve more space to avoid frequent reallocations.
196+
///
194197
/// At first, we have no memory allocated at all, but as we append to the
195198
/// string, it increases its capacity appropriately. If we instead use the
196199
/// [`with_capacity()`] method to allocate the correct capacity initially:
@@ -219,7 +222,9 @@ use boxed::Box;
219222
/// 25
220223
/// ```
221224
///
222-
/// Here, there's no need to allocate more memory inside the loop.
225+
/// Here, there's no need to allocate more memory inside the loop. As above,
226+
/// the actual numbers may differ as the allocator may reserve more space to
227+
/// avoid frequent reallocations.
223228
#[derive(PartialOrd, Eq, Ord)]
224229
#[stable(feature = "rust1", since = "1.0.0")]
225230
pub struct String {

src/libcollections/vec.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -207,9 +207,9 @@ use super::range::RangeArgument;
207207
/// strategy is used will of course guarantee `O(1)` amortized `push`.
208208
///
209209
/// `vec![x; n]`, `vec![a, b, c, d]`, and `Vec::with_capacity(n)`, will all
210-
/// produce a Vec with exactly the requested capacity. If `len() == capacity()`,
211-
/// (as is the case for the `vec!` macro), then a `Vec<T>` can be converted
212-
/// to and from a `Box<[T]>` without reallocating or moving the elements.
210+
/// produce a Vec with at least the requested capacity.
211+
/// If `len() == capacity()`, then a `Vec<T>` can be converted to and from a
212+
/// `Box<[T]>` without reallocating or moving the elements.
213213
///
214214
/// Vec will not specifically overwrite any data that is removed from it,
215215
/// but also won't specifically preserve it. Its uninitialized memory is

src/test/auxiliary/allocator-dummy.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,5 @@ pub extern fn __rust_reallocate_inplace(ptr: *mut u8, old_size: usize,
5151

5252
#[no_mangle]
5353
pub extern fn __rust_usable_size(size: usize, align: usize) -> usize {
54-
unsafe { core::intrinsics::abort() }
54+
size
5555
}

0 commit comments

Comments
 (0)