Skip to content

Commit f4b9dc3

Browse files
committed
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 in most cases, 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.
1 parent 34cce58 commit f4b9dc3

File tree

3 files changed

+141
-5
lines changed

3 files changed

+141
-5
lines changed

src/liballoc/raw_vec.rs

+25-4
Original file line numberDiff line numberDiff line change
@@ -407,13 +407,34 @@ impl<T, A: AllocRef> RawVec<T, A> {
407407
return Err(CapacityOverflow);
408408
}
409409

410+
if needed_extra_capacity == 0 {
411+
return Ok(());
412+
}
413+
410414
// Nothing we can really do about these checks, sadly.
411415
let required_cap =
412416
used_capacity.checked_add(needed_extra_capacity).ok_or(CapacityOverflow)?;
413-
// Cannot overflow, because `cap <= isize::MAX`, and type of `cap` is `usize`.
414-
let double_cap = self.cap * 2;
415-
// `double_cap` guarantees exponential growth.
416-
let cap = cmp::max(double_cap, required_cap);
417+
418+
// This guarantees exponential growth. The doubling cannot overflow
419+
// because `cap <= isize::MAX` and the type of `cap` is `usize`.
420+
let cap = cmp::max(self.cap * 2, required_cap);
421+
422+
// Tiny Vecs are dumb. Skip to:
423+
// - 8 if the element size is 1, because any heap allocators is likely
424+
// to round up a request of less than 8 bytes to at least 8 bytes.
425+
// - 4 if elements are moderate-sized (<= 1 KiB).
426+
// - 1 otherwise, to avoid wasting too much space for very short Vecs.
427+
// Note that `min_non_zero_cap` is computed statically.
428+
let elem_size = mem::size_of::<T>();
429+
let min_non_zero_cap = if elem_size == 1 {
430+
8
431+
} else if elem_size <= 1024 {
432+
4
433+
} else {
434+
1
435+
};
436+
let cap = cmp::max(min_non_zero_cap, cap);
437+
417438
let new_layout = Layout::array::<T>(cap);
418439

419440
// `finish_grow` is non-generic over `T`.

src/liballoc/raw_vec/tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ fn reserve_does_not_overallocate() {
5959
let mut v: RawVec<u32> = RawVec::new();
6060
v.reserve(0, 7);
6161
assert_eq!(7, v.capacity());
62-
// 97 if more than double of 7, so `reserve` should work
62+
// 97 is more than double of 7, so `reserve` should work
6363
// like `reserve_exact`.
6464
v.reserve(7, 90);
6565
assert_eq!(97, v.capacity());

src/liballoc/tests/vec.rs

+115
Original file line numberDiff line numberDiff line change
@@ -1473,3 +1473,118 @@ fn vec_macro_repeating_null_raw_fat_pointer() {
14731473
vtable: *mut (),
14741474
}
14751475
}
1476+
1477+
// This test will likely fail if you change the capacities used in
1478+
// `RawVec::grow_amortized`.
1479+
#[test]
1480+
fn test_push_growth_strategy() {
1481+
// If the element size is 1, we jump from 0 to 8, then double.
1482+
{
1483+
let mut v1: Vec<u8> = vec![];
1484+
assert_eq!(v1.capacity(), 0);
1485+
1486+
for _ in 0..8 {
1487+
v1.push(0);
1488+
assert_eq!(v1.capacity(), 8);
1489+
}
1490+
1491+
for _ in 8..16 {
1492+
v1.push(0);
1493+
assert_eq!(v1.capacity(), 16);
1494+
}
1495+
1496+
for _ in 16..32 {
1497+
v1.push(0);
1498+
assert_eq!(v1.capacity(), 32);
1499+
}
1500+
1501+
for _ in 32..64 {
1502+
v1.push(0);
1503+
assert_eq!(v1.capacity(), 64);
1504+
}
1505+
}
1506+
1507+
// If the element size is 2..=1024, we jump from 0 to 4, then double.
1508+
{
1509+
let mut v2: Vec<u16> = vec![];
1510+
let mut v1024: Vec<[u8; 1024]> = vec![];
1511+
assert_eq!(v2.capacity(), 0);
1512+
assert_eq!(v1024.capacity(), 0);
1513+
1514+
for _ in 0..4 {
1515+
v2.push(0);
1516+
v1024.push([0; 1024]);
1517+
assert_eq!(v2.capacity(), 4);
1518+
assert_eq!(v1024.capacity(), 4);
1519+
}
1520+
1521+
for _ in 4..8 {
1522+
v2.push(0);
1523+
v1024.push([0; 1024]);
1524+
assert_eq!(v2.capacity(), 8);
1525+
assert_eq!(v1024.capacity(), 8);
1526+
}
1527+
1528+
for _ in 8..16 {
1529+
v2.push(0);
1530+
v1024.push([0; 1024]);
1531+
assert_eq!(v2.capacity(), 16);
1532+
assert_eq!(v1024.capacity(), 16);
1533+
}
1534+
1535+
for _ in 16..32 {
1536+
v2.push(0);
1537+
v1024.push([0; 1024]);
1538+
assert_eq!(v2.capacity(), 32);
1539+
assert_eq!(v1024.capacity(), 32);
1540+
}
1541+
1542+
for _ in 32..64 {
1543+
v2.push(0);
1544+
v1024.push([0; 1024]);
1545+
assert_eq!(v2.capacity(), 64);
1546+
assert_eq!(v1024.capacity(), 64);
1547+
}
1548+
}
1549+
1550+
// If the element size is > 1024, we jump from 0 to 1, then double.
1551+
{
1552+
let mut v1025: Vec<[u8; 1025]> = vec![];
1553+
assert_eq!(v1025.capacity(), 0);
1554+
1555+
for _ in 0..1 {
1556+
v1025.push([0; 1025]);
1557+
assert_eq!(v1025.capacity(), 1);
1558+
}
1559+
1560+
for _ in 1..2 {
1561+
v1025.push([0; 1025]);
1562+
assert_eq!(v1025.capacity(), 2);
1563+
}
1564+
1565+
for _ in 2..4 {
1566+
v1025.push([0; 1025]);
1567+
assert_eq!(v1025.capacity(), 4);
1568+
}
1569+
1570+
for _ in 4..8 {
1571+
v1025.push([0; 1025]);
1572+
assert_eq!(v1025.capacity(), 8);
1573+
}
1574+
1575+
for _ in 8..16 {
1576+
v1025.push([0; 1025]);
1577+
assert_eq!(v1025.capacity(), 16);
1578+
}
1579+
1580+
for _ in 16..32 {
1581+
v1025.push([0; 1025]);
1582+
assert_eq!(v1025.capacity(), 32);
1583+
}
1584+
1585+
for _ in 32..64 {
1586+
v1025.push([0; 1025]);
1587+
assert_eq!(v1025.capacity(), 64);
1588+
}
1589+
}
1590+
}

0 commit comments

Comments
 (0)