Fix excessive memory allocation in RawVec::reserve
Before this patch `reserve` function allocated twice as requested
amount elements (not twice as capacity). It leaded to unnecessary
excessive memory usage in scenarios like this:
```
let mut v = Vec::new();
v.push(17);
v.extend(0..10);
println!("{}", v.capacity());
```
`Vec` allocated 22 elements, while it could allocate just 11.
`reserve` function must have a property of keeping `push` operation
cost (which calls `reserve`) `O(1)`. To achieve this `reserve` must
exponentialy grow its capacity when it does reallocation.
There's better strategy to implement `reserve`:
```
let new_capacity = max(current_capacity * 2, requested_capacity);
```
This strategy still guarantees that capacity grows at `O(1)` with
`reserve`, and fixes the issue with `extend`.
Patch imlpements this strategy.
This commit is contained in:
@@ -15,6 +15,7 @@ use heap;
|
||||
use super::oom;
|
||||
use super::boxed::Box;
|
||||
use core::ops::Drop;
|
||||
use core::cmp;
|
||||
use core;
|
||||
|
||||
/// A low-level utility for more ergonomically allocating, reallocating, and deallocating a
|
||||
@@ -360,9 +361,15 @@ impl<T> RawVec<T> {
|
||||
}
|
||||
|
||||
// Nothing we can really do about these checks :(
|
||||
let new_cap = used_cap.checked_add(needed_extra_cap)
|
||||
.and_then(|cap| cap.checked_mul(2))
|
||||
.expect("capacity overflow");
|
||||
let required_cap = used_cap.checked_add(needed_extra_cap)
|
||||
.expect("capacity overflow");
|
||||
|
||||
// Cannot overflow, because `cap <= isize::MAX`, and type of `cap` is `usize`.
|
||||
let double_cap = self.cap * 2;
|
||||
|
||||
// `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);
|
||||
@@ -486,3 +493,42 @@ fn alloc_guard(alloc_size: usize) {
|
||||
"capacity overflow");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn reserve_does_not_overallocate() {
|
||||
{
|
||||
let mut v: RawVec<u32> = RawVec::new();
|
||||
// First `reserve` allocates like `reserve_exact`
|
||||
v.reserve(0, 9);
|
||||
assert_eq!(9, v.cap());
|
||||
}
|
||||
|
||||
{
|
||||
let mut v: RawVec<u32> = RawVec::new();
|
||||
v.reserve(0, 7);
|
||||
assert_eq!(7, v.cap());
|
||||
// 97 if more than double of 7, so `reserve` should work
|
||||
// like `reserve_exact`.
|
||||
v.reserve(7, 90);
|
||||
assert_eq!(97, v.cap());
|
||||
}
|
||||
|
||||
{
|
||||
let mut v: RawVec<u32> = RawVec::new();
|
||||
v.reserve(0, 12);
|
||||
assert_eq!(12, v.cap());
|
||||
v.reserve(12, 3);
|
||||
// 3 is less than half of 12, so `reserve` must grow
|
||||
// exponentially. At the time of writing this test grow
|
||||
// factor is 2, so new capacity is 24, however, grow factor
|
||||
// of 1.5 is OK too. Hence `>= 18` in assert.
|
||||
assert!(v.cap() >= 12 + 12 / 2);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user