Skip to content

Commit

Permalink
Auto merge of rust-lang#72227 - nnethercote:tiny-vecs-are-dumb, r=Ama…
Browse files Browse the repository at this point in the history
…nieu

Tiny Vecs are dumb.

Currently, if you repeatedly push to an empty vector, the capacity
growth sequence is 0, 1, 2, 4, 8, 16, etc. This commit changes the
relevant code (the "amortized" growth strategy) to skip 1 and 2, instead
using 0, 4, 8, 16, etc. (You can still get a capacity of 1 or 2 using
the "exact" growth strategy, e.g. via `reserve_exact()`.)

This idea (along with the phrase "tiny Vecs are dumb") comes from the
"doubling" growth strategy that was removed from `RawVec` in rust-lang#72013.
That strategy was barely ever used -- only when a `VecDeque` was grown,
oddly enough -- which is why it was removed in rust-lang#72013.

(Fun fact: until just a few days ago, I thought the "doubling" strategy
was used for repeated push case. In other words, this commit makes
`Vec`s behave the way I always thought they behaved.)

This change reduces the number of allocations done by rustc itself by
10% or more. It speeds up rustc, and will also speed up any other Rust
program that uses `Vec`s a lot.

In theory, the change could increase memory usage, but in practice it
doesn't. It would be an unusual program where very small `Vec`s having a
capacity of 4 rather than 1 or 2 would make a difference. You'd need a
*lot* of very small `Vec`s, and/or some very small `Vec`s with very
large elements.

r? @Amanieu
  • Loading branch information
bors committed May 19, 2020
2 parents 42acd90 + f4b9dc3 commit 672b272
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 5 deletions.
29 changes: 25 additions & 4 deletions src/liballoc/raw_vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -407,13 +407,34 @@ impl<T, A: AllocRef> RawVec<T, A> {
return Err(CapacityOverflow);
}

if needed_extra_capacity == 0 {
return Ok(());
}

// Nothing we can really do about these checks, sadly.
let required_cap =
used_capacity.checked_add(needed_extra_capacity).ok_or(CapacityOverflow)?;
// Cannot overflow, because `cap <= isize::MAX`, and type of `cap` is `usize`.
let double_cap = self.cap * 2;
// `double_cap` guarantees exponential growth.
let cap = cmp::max(double_cap, required_cap);

// This guarantees exponential growth. The doubling cannot overflow
// because `cap <= isize::MAX` and the type of `cap` is `usize`.
let cap = cmp::max(self.cap * 2, required_cap);

// Tiny Vecs are dumb. Skip to:
// - 8 if the element size is 1, because any heap allocators is likely
// to round up a request of less than 8 bytes to at least 8 bytes.
// - 4 if elements are moderate-sized (<= 1 KiB).
// - 1 otherwise, to avoid wasting too much space for very short Vecs.
// Note that `min_non_zero_cap` is computed statically.
let elem_size = mem::size_of::<T>();
let min_non_zero_cap = if elem_size == 1 {
8
} else if elem_size <= 1024 {
4
} else {
1
};
let cap = cmp::max(min_non_zero_cap, cap);

let new_layout = Layout::array::<T>(cap);

// `finish_grow` is non-generic over `T`.
Expand Down
2 changes: 1 addition & 1 deletion src/liballoc/raw_vec/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ fn reserve_does_not_overallocate() {
let mut v: RawVec<u32> = RawVec::new();
v.reserve(0, 7);
assert_eq!(7, v.capacity());
// 97 if more than double of 7, so `reserve` should work
// 97 is more than double of 7, so `reserve` should work
// like `reserve_exact`.
v.reserve(7, 90);
assert_eq!(97, v.capacity());
Expand Down
115 changes: 115 additions & 0 deletions src/liballoc/tests/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1473,3 +1473,118 @@ fn vec_macro_repeating_null_raw_fat_pointer() {
vtable: *mut (),
}
}

// This test will likely fail if you change the capacities used in
// `RawVec::grow_amortized`.
#[test]
fn test_push_growth_strategy() {
// If the element size is 1, we jump from 0 to 8, then double.
{
let mut v1: Vec<u8> = vec![];
assert_eq!(v1.capacity(), 0);

for _ in 0..8 {
v1.push(0);
assert_eq!(v1.capacity(), 8);
}

for _ in 8..16 {
v1.push(0);
assert_eq!(v1.capacity(), 16);
}

for _ in 16..32 {
v1.push(0);
assert_eq!(v1.capacity(), 32);
}

for _ in 32..64 {
v1.push(0);
assert_eq!(v1.capacity(), 64);
}
}

// If the element size is 2..=1024, we jump from 0 to 4, then double.
{
let mut v2: Vec<u16> = vec![];
let mut v1024: Vec<[u8; 1024]> = vec![];
assert_eq!(v2.capacity(), 0);
assert_eq!(v1024.capacity(), 0);

for _ in 0..4 {
v2.push(0);
v1024.push([0; 1024]);
assert_eq!(v2.capacity(), 4);
assert_eq!(v1024.capacity(), 4);
}

for _ in 4..8 {
v2.push(0);
v1024.push([0; 1024]);
assert_eq!(v2.capacity(), 8);
assert_eq!(v1024.capacity(), 8);
}

for _ in 8..16 {
v2.push(0);
v1024.push([0; 1024]);
assert_eq!(v2.capacity(), 16);
assert_eq!(v1024.capacity(), 16);
}

for _ in 16..32 {
v2.push(0);
v1024.push([0; 1024]);
assert_eq!(v2.capacity(), 32);
assert_eq!(v1024.capacity(), 32);
}

for _ in 32..64 {
v2.push(0);
v1024.push([0; 1024]);
assert_eq!(v2.capacity(), 64);
assert_eq!(v1024.capacity(), 64);
}
}

// If the element size is > 1024, we jump from 0 to 1, then double.
{
let mut v1025: Vec<[u8; 1025]> = vec![];
assert_eq!(v1025.capacity(), 0);

for _ in 0..1 {
v1025.push([0; 1025]);
assert_eq!(v1025.capacity(), 1);
}

for _ in 1..2 {
v1025.push([0; 1025]);
assert_eq!(v1025.capacity(), 2);
}

for _ in 2..4 {
v1025.push([0; 1025]);
assert_eq!(v1025.capacity(), 4);
}

for _ in 4..8 {
v1025.push([0; 1025]);
assert_eq!(v1025.capacity(), 8);
}

for _ in 8..16 {
v1025.push([0; 1025]);
assert_eq!(v1025.capacity(), 16);
}

for _ in 16..32 {
v1025.push([0; 1025]);
assert_eq!(v1025.capacity(), 32);
}

for _ in 32..64 {
v1025.push([0; 1025]);
assert_eq!(v1025.capacity(), 64);
}
}
}

0 comments on commit 672b272

Please sign in to comment.