diff --git a/src/liballoc/raw_vec.rs b/src/liballoc/raw_vec.rs index 92f35c08a7deb..fb30e2eb6873f 100644 --- a/src/liballoc/raw_vec.rs +++ b/src/liballoc/raw_vec.rs @@ -99,6 +99,8 @@ impl RawVec { heap::EMPTY as *mut u8 } else { let align = mem::align_of::(); + // FIXME: we do not round up the capacity here, pending the + // discussion in #29931 let ptr = heap::allocate(alloc_size, align); if ptr.is_null() { oom() @@ -215,14 +217,13 @@ impl RawVec { } else { 4 }; - let ptr = heap::allocate(new_cap * elem_size, align); + let (alloc_size, new_cap) = round_up_alloc_size::(new_cap); + let ptr = heap::allocate(alloc_size, align); (new_cap, ptr) } else { // Since we guarantee that we never allocate more than isize::MAX bytes, // `elem_size * self.cap <= isize::MAX` as a precondition, so this can't overflow - let new_cap = 2 * self.cap; - let new_alloc_size = new_cap * elem_size; - alloc_guard(new_alloc_size); + let (new_alloc_size, new_cap) = round_up_alloc_size::(2 * self.cap); let ptr = heap::reallocate(self.ptr() as *mut _, self.cap * elem_size, new_alloc_size, @@ -278,8 +279,7 @@ impl RawVec { // Nothing we can really do about these checks :( let new_cap = used_cap.checked_add(needed_extra_cap).expect("capacity overflow"); - let new_alloc_size = new_cap.checked_mul(elem_size).expect("capacity overflow"); - alloc_guard(new_alloc_size); + let (new_alloc_size, new_cap) = round_up_alloc_size::(new_cap); let ptr = if self.cap == 0 { heap::allocate(new_alloc_size, align) @@ -370,9 +370,7 @@ impl RawVec { // `double_cap` guarantees exponential growth. let new_cap = cmp::max(double_cap, required_cap); - let new_alloc_size = new_cap.checked_mul(elem_size).expect("capacity overflow"); - // FIXME: may crash and burn on over-reserve - alloc_guard(new_alloc_size); + let (new_alloc_size, new_cap) = round_up_alloc_size::(new_cap); let ptr = if self.cap == 0 { heap::allocate(new_alloc_size, align) @@ -422,6 +420,9 @@ impl RawVec { unsafe { // Overflow check is unnecessary as the vector is already at // least this large. + // FIXME: pending the discussion in #29931, we do not round up + // the capacity here, since it might break assumptions for + // example in Vec::into_boxed_slice let ptr = heap::reallocate(self.ptr() as *mut _, self.cap * elem_size, amount * elem_size, @@ -475,7 +476,24 @@ impl Drop for RawVec { } } +// The system allocator may actually give us more memory than we requested. +// The allocator usually gives a power-of-two, so instead of asking the +// allocator via `heap::usable_size` which would incur some overhead, we rather +// round up the request ourselves to the nearest power-of-two + +#[inline] +fn round_up_alloc_size(cap: usize) -> (usize, usize) { + let elem_size = mem::size_of::(); + + let alloc_size = cap.checked_mul(elem_size) + .and_then(usize::checked_next_power_of_two).expect("capacity overflow"); + alloc_guard(alloc_size); + + let cap = alloc_size / elem_size; + let alloc_size = cap * elem_size; + (alloc_size, cap) +} // We need to guarantee the following: // * We don't ever allocate `> isize::MAX` byte-size objects @@ -501,27 +519,29 @@ mod tests { #[test] fn reserve_does_not_overallocate() { + // NB: when rounding up allocation sizes, the cap is not exact + // but uses the whole memory allocated by the system allocator { let mut v: RawVec = RawVec::new(); // First `reserve` allocates like `reserve_exact` v.reserve(0, 9); - assert_eq!(9, v.cap()); + assert!(v.cap() >= 9 && v.cap() <= 9 * 2); } { let mut v: RawVec = RawVec::new(); v.reserve(0, 7); - assert_eq!(7, v.cap()); + assert!(v.cap() >= 7 && v.cap() <= 7 * 2); // 97 if more than double of 7, so `reserve` should work // like `reserve_exact`. v.reserve(7, 90); - assert_eq!(97, v.cap()); + assert!(v.cap() >= 97 && v.cap() <= 97 * 2); } { let mut v: RawVec = RawVec::new(); v.reserve(0, 12); - assert_eq!(12, v.cap()); + assert!(v.cap() >= 12 && v.cap() <= 12 * 2); v.reserve(12, 3); // 3 is less than half of 12, so `reserve` must grow // exponentially. At the time of writing this test grow diff --git a/src/libcollections/string.rs b/src/libcollections/string.rs index a3c6918293477..eb5afc5c2e77f 100644 --- a/src/libcollections/string.rs +++ b/src/libcollections/string.rs @@ -148,7 +148,7 @@ use boxed::Box; /// let len = story.len(); /// let capacity = story.capacity(); /// -/// // story has thirteen bytes +/// // story has nineteen bytes /// assert_eq!(19, len); /// /// // Now that we have our parts, we throw the story away. @@ -191,6 +191,9 @@ use boxed::Box; /// 40 /// ``` /// +/// Please note that the actual output may be higher than stated as the +/// allocator may reserve more space to avoid frequent reallocations. +/// /// At first, we have no memory allocated at all, but as we append to the /// string, it increases its capacity appropriately. If we instead use the /// [`with_capacity()`] method to allocate the correct capacity initially: @@ -219,7 +222,9 @@ use boxed::Box; /// 25 /// ``` /// -/// Here, there's no need to allocate more memory inside the loop. +/// Here, there's no need to allocate more memory inside the loop. As above, +/// the actual numbers may differ as the allocator may reserve more space to +/// avoid frequent reallocations. #[derive(PartialOrd, Eq, Ord)] #[stable(feature = "rust1", since = "1.0.0")] pub struct String { @@ -763,7 +768,11 @@ impl String { self.vec.reserve_exact(additional) } - /// Shrinks the capacity of this string buffer to match its length. + /// Shrinks the capacity of this string buffer to match its length as much + /// as possible. + /// + /// It will drop down as close as possible to the length but the allocator + /// may still inform the string that there is space for a few more bytes. /// /// # Examples /// @@ -772,7 +781,7 @@ impl String { /// s.reserve(100); /// assert!(s.capacity() >= 100); /// s.shrink_to_fit(); - /// assert_eq!(s.capacity(), 3); + /// assert!(s.capacity() >= 3); /// ``` #[inline] #[stable(feature = "rust1", since = "1.0.0")] diff --git a/src/libcollections/vec.rs b/src/libcollections/vec.rs index ddad7533a081f..9ebb2b8080341 100644 --- a/src/libcollections/vec.rs +++ b/src/libcollections/vec.rs @@ -207,9 +207,9 @@ use super::range::RangeArgument; /// strategy is used will of course guarantee `O(1)` amortized `push`. /// /// `vec![x; n]`, `vec![a, b, c, d]`, and `Vec::with_capacity(n)`, will all -/// produce a Vec with exactly the requested capacity. If `len() == capacity()`, -/// (as is the case for the `vec!` macro), then a `Vec` can be converted -/// to and from a `Box<[T]>` without reallocating or moving the elements. +/// produce a Vec with at least the requested capacity. +/// If `len() == capacity()`, then a `Vec` can be converted to and from a +/// `Box<[T]>` without reallocating or moving the elements. /// /// Vec will not specifically overwrite any data that is removed from it, /// but also won't specifically preserve it. Its uninitialized memory is @@ -273,10 +273,13 @@ impl Vec { /// assert_eq!(vec.len(), 0); /// /// // These are all done without reallocating... + /// let cap = vec.capacity(); /// for i in 0..10 { /// vec.push(i); /// } /// + /// assert_eq!(vec.capacity(), cap); + /// /// // ...but this may make the vector reallocate /// vec.push(11); /// ``` @@ -349,7 +352,7 @@ impl Vec { /// /// ``` /// let vec: Vec = Vec::with_capacity(10); - /// assert_eq!(vec.capacity(), 10); + /// assert!(vec.capacity() >= 10); /// ``` #[inline] #[stable(feature = "rust1", since = "1.0.0")] @@ -411,7 +414,7 @@ impl Vec { /// ``` /// let mut vec = Vec::with_capacity(10); /// vec.extend([1, 2, 3].iter().cloned()); - /// assert_eq!(vec.capacity(), 10); + /// assert!(vec.capacity() >= 10); /// vec.shrink_to_fit(); /// assert!(vec.capacity() >= 3); /// ``` diff --git a/src/test/auxiliary/allocator-dummy.rs b/src/test/auxiliary/allocator-dummy.rs index a1d21db8f4d5d..dfe596d3a6106 100644 --- a/src/test/auxiliary/allocator-dummy.rs +++ b/src/test/auxiliary/allocator-dummy.rs @@ -51,5 +51,5 @@ pub extern fn __rust_reallocate_inplace(ptr: *mut u8, old_size: usize, #[no_mangle] pub extern fn __rust_usable_size(size: usize, align: usize) -> usize { - unsafe { core::intrinsics::abort() } + size }