Skip to content

Commit 75e1463

Browse files
committed
Auto merge of rust-lang#72013 - nnethercote:make-RawVec-grow-mostly-non-generic, r=Amanieu
Make `RawVec::grow` mostly non-generic. `cargo-llvm-lines` shows that, in various benchmarks, `RawVec::grow` is instantiated 10s or 100s of times and accounts for 1-8% of lines of generated LLVM IR. This commit moves most of `RawVec::grow` into a separate function that isn't parameterized by `T`, which means it doesn't need to be instantiated many times. This reduces compile time significantly. r? @ghost
2 parents 750db09 + 68b7503 commit 75e1463

File tree

2 files changed

+95
-149
lines changed

2 files changed

+95
-149
lines changed

src/liballoc/collections/vec_deque.rs

+14-6
Original file line numberDiff line numberDiff line change
@@ -1354,7 +1354,9 @@ impl<T> VecDeque<T> {
13541354
/// ```
13551355
#[stable(feature = "rust1", since = "1.0.0")]
13561356
pub fn push_front(&mut self, value: T) {
1357-
self.grow_if_necessary();
1357+
if self.is_full() {
1358+
self.grow();
1359+
}
13581360

13591361
self.tail = self.wrap_sub(self.tail, 1);
13601362
let tail = self.tail;
@@ -1377,7 +1379,9 @@ impl<T> VecDeque<T> {
13771379
/// ```
13781380
#[stable(feature = "rust1", since = "1.0.0")]
13791381
pub fn push_back(&mut self, value: T) {
1380-
self.grow_if_necessary();
1382+
if self.is_full() {
1383+
self.grow();
1384+
}
13811385

13821386
let head = self.head;
13831387
self.head = self.wrap_add(self.head, 1);
@@ -1485,7 +1489,9 @@ impl<T> VecDeque<T> {
14851489
#[stable(feature = "deque_extras_15", since = "1.5.0")]
14861490
pub fn insert(&mut self, index: usize, value: T) {
14871491
assert!(index <= self.len(), "index out of bounds");
1488-
self.grow_if_necessary();
1492+
if self.is_full() {
1493+
self.grow();
1494+
}
14891495

14901496
// Move the least number of elements in the ring buffer and insert
14911497
// the given object
@@ -2003,11 +2009,13 @@ impl<T> VecDeque<T> {
20032009
}
20042010

20052011
// This may panic or abort
2006-
#[inline]
2007-
fn grow_if_necessary(&mut self) {
2012+
#[inline(never)]
2013+
fn grow(&mut self) {
20082014
if self.is_full() {
20092015
let old_cap = self.cap();
2010-
self.buf.double();
2016+
// Double the buffer size.
2017+
self.buf.reserve_exact(old_cap, old_cap);
2018+
assert!(self.cap() == old_cap * 2);
20112019
unsafe {
20122020
self.handle_capacity_increase(old_cap);
20132021
}

src/liballoc/raw_vec.rs

+81-143
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![unstable(feature = "raw_vec_internals", reason = "implementation detail", issue = "none")]
22
#![doc(hidden)]
33

4-
use core::alloc::MemoryBlock;
4+
use core::alloc::{LayoutErr, MemoryBlock};
55
use core::cmp;
66
use core::mem::{self, ManuallyDrop, MaybeUninit};
77
use core::ops::Drop;
@@ -211,82 +211,6 @@ impl<T, A: AllocRef> RawVec<T, A> {
211211
}
212212
}
213213

214-
/// Doubles the size of the type's backing allocation. This is common enough
215-
/// to want to do that it's easiest to just have a dedicated method. Slightly
216-
/// more efficient logic can be provided for this than the general case.
217-
///
218-
/// This function is ideal for when pushing elements one-at-a-time because
219-
/// you don't need to incur the costs of the more general computations
220-
/// reserve needs to do to guard against overflow. You do however need to
221-
/// manually check if your `len == capacity`.
222-
///
223-
/// # Panics
224-
///
225-
/// * Panics if `T` is zero-sized on the assumption that you managed to exhaust
226-
/// all `usize::MAX` slots in your imaginary buffer.
227-
/// * Panics on 32-bit platforms if the requested capacity exceeds
228-
/// `isize::MAX` bytes.
229-
///
230-
/// # Aborts
231-
///
232-
/// Aborts on OOM
233-
///
234-
/// # Examples
235-
///
236-
/// ```
237-
/// # #![feature(raw_vec_internals)]
238-
/// # extern crate alloc;
239-
/// # use std::ptr;
240-
/// # use alloc::raw_vec::RawVec;
241-
/// struct MyVec<T> {
242-
/// buf: RawVec<T>,
243-
/// len: usize,
244-
/// }
245-
///
246-
/// impl<T> MyVec<T> {
247-
/// pub fn push(&mut self, elem: T) {
248-
/// if self.len == self.buf.capacity() { self.buf.double(); }
249-
/// // double would have aborted or panicked if the len exceeded
250-
/// // `isize::MAX` so this is safe to do unchecked now.
251-
/// unsafe {
252-
/// ptr::write(self.buf.ptr().add(self.len), elem);
253-
/// }
254-
/// self.len += 1;
255-
/// }
256-
/// }
257-
/// # fn main() {
258-
/// # let mut vec = MyVec { buf: RawVec::new(), len: 0 };
259-
/// # vec.push(1);
260-
/// # }
261-
/// ```
262-
#[inline(never)]
263-
#[cold]
264-
pub fn double(&mut self) {
265-
match self.grow(Double, MayMove, Uninitialized) {
266-
Err(CapacityOverflow) => capacity_overflow(),
267-
Err(AllocError { layout, .. }) => handle_alloc_error(layout),
268-
Ok(()) => { /* yay */ }
269-
}
270-
}
271-
272-
/// Attempts to double the size of the type's backing allocation in place. This is common
273-
/// enough to want to do that it's easiest to just have a dedicated method. Slightly
274-
/// more efficient logic can be provided for this than the general case.
275-
///
276-
/// Returns `true` if the reallocation attempt has succeeded.
277-
///
278-
/// # Panics
279-
///
280-
/// * Panics if `T` is zero-sized on the assumption that you managed to exhaust
281-
/// all `usize::MAX` slots in your imaginary buffer.
282-
/// * Panics on 32-bit platforms if the requested capacity exceeds
283-
/// `isize::MAX` bytes.
284-
#[inline(never)]
285-
#[cold]
286-
pub fn double_in_place(&mut self) -> bool {
287-
self.grow(Double, InPlace, Uninitialized).is_ok()
288-
}
289-
290214
/// Ensures that the buffer contains at least enough space to hold
291215
/// `used_capacity + needed_extra_capacity` elements. If it doesn't already have
292216
/// enough capacity, will reallocate enough space plus comfortable slack
@@ -354,7 +278,7 @@ impl<T, A: AllocRef> RawVec<T, A> {
354278
needed_extra_capacity: usize,
355279
) -> Result<(), TryReserveError> {
356280
if self.needs_to_grow(used_capacity, needed_extra_capacity) {
357-
self.grow(Amortized { used_capacity, needed_extra_capacity }, MayMove, Uninitialized)
281+
self.grow_amortized(used_capacity, needed_extra_capacity, MayMove)
358282
} else {
359283
Ok(())
360284
}
@@ -381,8 +305,7 @@ impl<T, A: AllocRef> RawVec<T, A> {
381305
// This is more readable than putting this in one line:
382306
// `!self.needs_to_grow(...) || self.grow(...).is_ok()`
383307
if self.needs_to_grow(used_capacity, needed_extra_capacity) {
384-
self.grow(Amortized { used_capacity, needed_extra_capacity }, InPlace, Uninitialized)
385-
.is_ok()
308+
self.grow_amortized(used_capacity, needed_extra_capacity, InPlace).is_ok()
386309
} else {
387310
true
388311
}
@@ -423,7 +346,7 @@ impl<T, A: AllocRef> RawVec<T, A> {
423346
needed_extra_capacity: usize,
424347
) -> Result<(), TryReserveError> {
425348
if self.needs_to_grow(used_capacity, needed_extra_capacity) {
426-
self.grow(Exact { used_capacity, needed_extra_capacity }, MayMove, Uninitialized)
349+
self.grow_exact(used_capacity, needed_extra_capacity)
427350
} else {
428351
Ok(())
429352
}
@@ -448,14 +371,6 @@ impl<T, A: AllocRef> RawVec<T, A> {
448371
}
449372
}
450373

451-
#[derive(Copy, Clone)]
452-
enum Strategy {
453-
Double,
454-
Amortized { used_capacity: usize, needed_extra_capacity: usize },
455-
Exact { used_capacity: usize, needed_extra_capacity: usize },
456-
}
457-
use Strategy::*;
458-
459374
impl<T, A: AllocRef> RawVec<T, A> {
460375
/// Returns if the buffer needs to grow to fulfill the needed extra capacity.
461376
/// Mainly used to make inlining reserve-calls possible without inlining `grow`.
@@ -473,68 +388,59 @@ impl<T, A: AllocRef> RawVec<T, A> {
473388
self.cap = Self::capacity_from_bytes(memory.size);
474389
}
475390

476-
/// Single method to handle all possibilities of growing the buffer.
477-
fn grow(
391+
// This method is usually instantiated many times. So we want it to be as
392+
// small as possible, to improve compile times. But we also want as much of
393+
// its contents to be statically computable as possible, to make the
394+
// generated code run faster. Therefore, this method is carefully written
395+
// so that all of the code that depends on `T` is within it, while as much
396+
// of the code that doesn't depend on `T` as possible is in functions that
397+
// are non-generic over `T`.
398+
fn grow_amortized(
478399
&mut self,
479-
strategy: Strategy,
400+
used_capacity: usize,
401+
needed_extra_capacity: usize,
480402
placement: ReallocPlacement,
481-
init: AllocInit,
482403
) -> Result<(), TryReserveError> {
483-
let elem_size = mem::size_of::<T>();
484-
if elem_size == 0 {
404+
if mem::size_of::<T>() == 0 {
485405
// Since we return a capacity of `usize::MAX` when `elem_size` is
486406
// 0, getting to here necessarily means the `RawVec` is overfull.
487407
return Err(CapacityOverflow);
488408
}
489-
let new_layout = match strategy {
490-
Double => unsafe {
491-
// Since we guarantee that we never allocate more than `isize::MAX` bytes,
492-
// `elem_size * self.cap <= isize::MAX` as a precondition, so this can't overflow.
493-
// Additionally the alignment will never be too large as to "not be satisfiable",
494-
// so `Layout::from_size_align` will always return `Some`.
495-
//
496-
// TL;DR, we bypass runtime checks due to dynamic assertions in this module,
497-
// allowing us to use `from_size_align_unchecked`.
498-
let cap = if self.cap == 0 {
499-
// Skip to 4 because tiny `Vec`'s are dumb; but not if that would cause overflow.
500-
if elem_size > usize::MAX / 8 { 1 } else { 4 }
501-
} else {
502-
self.cap * 2
503-
};
504-
Layout::from_size_align_unchecked(cap * elem_size, mem::align_of::<T>())
505-
},
506-
Amortized { used_capacity, needed_extra_capacity } => {
507-
// Nothing we can really do about these checks, sadly.
508-
let required_cap =
509-
used_capacity.checked_add(needed_extra_capacity).ok_or(CapacityOverflow)?;
510-
// Cannot overflow, because `cap <= isize::MAX`, and type of `cap` is `usize`.
511-
let double_cap = self.cap * 2;
512-
// `double_cap` guarantees exponential growth.
513-
let cap = cmp::max(double_cap, required_cap);
514-
Layout::array::<T>(cap).map_err(|_| CapacityOverflow)?
515-
}
516-
Exact { used_capacity, needed_extra_capacity } => {
517-
let cap =
518-
used_capacity.checked_add(needed_extra_capacity).ok_or(CapacityOverflow)?;
519-
Layout::array::<T>(cap).map_err(|_| CapacityOverflow)?
520-
}
521-
};
522-
alloc_guard(new_layout.size())?;
523409

524-
let memory = if let Some((ptr, old_layout)) = self.current_memory() {
525-
debug_assert_eq!(old_layout.align(), new_layout.align());
526-
unsafe {
527-
self.alloc
528-
.grow(ptr, old_layout, new_layout.size(), placement, init)
529-
.map_err(|_| AllocError { layout: new_layout, non_exhaustive: () })?
530-
}
531-
} else {
532-
match placement {
533-
MayMove => self.alloc.alloc(new_layout, init),
534-
InPlace => Err(AllocErr),
535-
}
536-
.map_err(|_| AllocError { layout: new_layout, non_exhaustive: () })?
537-
};
410+
// Nothing we can really do about these checks, sadly.
411+
let required_cap =
412+
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+
let new_layout = Layout::array::<T>(cap);
418+
419+
// `finish_grow` is non-generic over `T`.
420+
let memory = finish_grow(new_layout, placement, self.current_memory(), &mut self.alloc)?;
421+
self.set_memory(memory);
422+
Ok(())
423+
}
424+
425+
// The constraints on this method are much the same as those on
426+
// `grow_amortized`, but this method is usually instantiated less often so
427+
// it's less critical.
428+
fn grow_exact(
429+
&mut self,
430+
used_capacity: usize,
431+
needed_extra_capacity: usize,
432+
) -> Result<(), TryReserveError> {
433+
if mem::size_of::<T>() == 0 {
434+
// Since we return a capacity of `usize::MAX` when the type size is
435+
// 0, getting to here necessarily means the `RawVec` is overfull.
436+
return Err(CapacityOverflow);
437+
}
438+
439+
let cap = used_capacity.checked_add(needed_extra_capacity).ok_or(CapacityOverflow)?;
440+
let new_layout = Layout::array::<T>(cap);
441+
442+
// `finish_grow` is non-generic over `T`.
443+
let memory = finish_grow(new_layout, MayMove, self.current_memory(), &mut self.alloc)?;
538444
self.set_memory(memory);
539445
Ok(())
540446
}
@@ -562,6 +468,38 @@ impl<T, A: AllocRef> RawVec<T, A> {
562468
}
563469
}
564470

471+
// This function is outside `RawVec` to minimize compile times. See the comment
472+
// above `RawVec::grow_amortized` for details. (The `A` parameter isn't
473+
// significant, because the number of different `A` types seen in practice is
474+
// much smaller than the number of `T` types.)
475+
fn finish_grow<A>(
476+
new_layout: Result<Layout, LayoutErr>,
477+
placement: ReallocPlacement,
478+
current_memory: Option<(NonNull<u8>, Layout)>,
479+
alloc: &mut A,
480+
) -> Result<MemoryBlock, TryReserveError>
481+
where
482+
A: AllocRef,
483+
{
484+
// Check for the error here to minimize the size of `RawVec::grow_*`.
485+
let new_layout = new_layout.map_err(|_| CapacityOverflow)?;
486+
487+
alloc_guard(new_layout.size())?;
488+
489+
let memory = if let Some((ptr, old_layout)) = current_memory {
490+
debug_assert_eq!(old_layout.align(), new_layout.align());
491+
unsafe { alloc.grow(ptr, old_layout, new_layout.size(), placement, Uninitialized) }
492+
} else {
493+
match placement {
494+
MayMove => alloc.alloc(new_layout, Uninitialized),
495+
InPlace => Err(AllocErr),
496+
}
497+
}
498+
.map_err(|_| AllocError { layout: new_layout, non_exhaustive: () })?;
499+
500+
Ok(memory)
501+
}
502+
565503
impl<T> RawVec<T, Global> {
566504
/// Converts the entire buffer into `Box<[MaybeUninit<T>]>` with the specified `len`.
567505
///

0 commit comments

Comments
 (0)