about summary refs log tree commit diff
path: root/src/liballoc
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2015-10-30 22:23:41 +0000
committerbors <bors@rust-lang.org>2015-10-30 22:23:41 +0000
commit64b027764302aa67aa701a9f81bd938ca3d4052a (patch)
tree99fc863475771ccdf0a411c6e9ac9ceb3f9c483f /src/liballoc
parentcc8d398e28b6b1918ef85479c2d040dfd0fe582d (diff)
parent46068c9dafe8cfa763ef855ec21f577a1e058de1 (diff)
downloadrust-64b027764302aa67aa701a9f81bd938ca3d4052a.tar.gz
rust-64b027764302aa67aa701a9f81bd938ca3d4052a.zip
Auto merge of #29454 - stepancheg:vec-reserve, r=bluss
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.
Diffstat (limited to 'src/liballoc')
-rw-r--r--src/liballoc/raw_vec.rs52
1 files changed, 49 insertions, 3 deletions
diff --git a/src/liballoc/raw_vec.rs b/src/liballoc/raw_vec.rs
index eef8f3e94e0..78deed0c84f 100644
--- a/src/liballoc/raw_vec.rs
+++ b/src/liballoc/raw_vec.rs
@@ -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);
+        }
+    }
+
+}