Skip to content

Commit dbc2ef8

Browse files
committed
Grow RawVec to fill the allocator bins tighter.
AFAIK, most if not not all memory allocators use some combination of evenly sized bins. Typically these bins are power of two sized, sometimes suplemented with some 0b1100xxx... bins as well. Most of the time every allocation is also prefixed with a pointer to some per-allocation metadata, adding a fixed overhead to every requested allocation. This can observed with: ```rust fn main() { let s = 24; let v1: Vec<u8> = Vec::with_capacity(s); let v2: Vec<u8> = Vec::with_capacity(s); let v3: Vec<u8> = Vec::with_capacity(s); println!("{:?}", v1.as_ptr()); println!("{:?}", v2.as_ptr()); println!("{:?}", v3.as_ptr()); } ``` https://play.rust-lang.org/?version=stable&mode=release&edition=2021&gist=e8cea915323ad742c9d78863e3884587 For `let s = 24` the pointers are 32 bytes appart, but increasing `s` to 25 make them 64 bytes appart, as the allocation falls into the one up sized bin. This made me think that the default way of growing collections in Rust (doubling their capacity) is degenerate in most common cases. Most types is aligned to the power of 2 size, and then doubling their size make almost every allocation waste almost 50% of actually allocated space for it. By growing the capacity by trying to fill the bin well, we can possibly avoid some needless allocations and lower the memory consumption.
1 parent 289279d commit dbc2ef8

File tree

2 files changed

+24
-5
lines changed

2 files changed

+24
-5
lines changed

library/alloc/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@
119119
#![feature(fn_traits)]
120120
#![feature(hasher_prefixfree_extras)]
121121
#![feature(inplace_iteration)]
122+
#![feature(int_roundings)]
122123
#![feature(iter_advance_by)]
123124
#![feature(iter_next_chunk)]
124125
#![feature(layout_for_ptr)]

library/alloc/src/raw_vec.rs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#![unstable(feature = "raw_vec_internals", reason = "unstable const warnings", issue = "none")]
22

33
use core::alloc::LayoutError;
4-
use core::cmp;
54
use core::intrinsics;
65
use core::mem::{self, ManuallyDrop, MaybeUninit};
76
use core::ops::Drop;
@@ -386,13 +385,32 @@ impl<T, A: Allocator> RawVec<T, A> {
386385
return Err(CapacityOverflow.into());
387386
}
388387

388+
// Size of allocator's per-allocation overhead we expect
389+
// FIXME: maybe two pointers to be on the safe side? It could potentially
390+
// be platform-dependent.
391+
let alloc_overhead_size = mem::size_of::<usize>();
392+
389393
// Nothing we can really do about these checks, sadly.
390394
let required_cap = len.checked_add(additional).ok_or(CapacityOverflow)?;
391395

392-
// This guarantees exponential growth. The doubling cannot overflow
393-
// because `cap <= isize::MAX` and the type of `cap` is `usize`.
394-
let cap = cmp::max(self.cap * 2, required_cap);
395-
let cap = cmp::max(Self::MIN_NON_ZERO_CAP, cap);
396+
let alloc_size = required_cap.checked_mul(mem::size_of::<T>()).ok_or(CapacityOverflow)?;
397+
// Add the overhead
398+
let alloc_size = alloc_size.checked_add(alloc_overhead_size).ok_or(CapacityOverflow)?;
399+
400+
// Since memory allocators tend to use power of two sized bins, find the
401+
// bin size we will fall into.
402+
debug_assert!(alloc_size > 1);
403+
let bin_size = usize::MAX >> (alloc_size - 1).leading_zeros(); // + 1 skipped to prevent overflow
404+
405+
// Leave some room for allocators that add fixed overhead (usually
406+
// one pointer-size)
407+
let aligned_alloc_size = bin_size.saturating_sub(alloc_overhead_size - 1) /* the +1 skipped from the previous line turned into -1 here */ ;
408+
409+
// Align the capacity to fit the bin
410+
let cap = aligned_alloc_size / mem::size_of::<T>();
411+
// Since we've added the overhead in `required_cap`, we shold never
412+
// end up with smaller cap after aligning
413+
debug_assert!(required_cap <= cap);
396414

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

0 commit comments

Comments
 (0)