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:
Stepan Koltsov 2015-10-31 00:17:16 +03:00
parent 914c4dbc2a
commit 46068c9daf

View File

@ -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);
}
}
}