Skip to content

Commit 68b7503

Browse files
committed
Split RawVec::grow up.
The amortized case is much more common than the exact case, and it is typically instantiated many times. Also, we can put a chunk of the code into a function that isn't generic over T, which reduces the amount of LLVM IR generated quite a lot, improving compile times.
1 parent f420726 commit 68b7503

File tree

1 file changed

+79
-50
lines changed

1 file changed

+79
-50
lines changed

src/liballoc/raw_vec.rs

+79-50
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;
@@ -278,7 +278,7 @@ impl<T, A: AllocRef> RawVec<T, A> {
278278
needed_extra_capacity: usize,
279279
) -> Result<(), TryReserveError> {
280280
if self.needs_to_grow(used_capacity, needed_extra_capacity) {
281-
self.grow(Amortized, used_capacity, needed_extra_capacity, MayMove, Uninitialized)
281+
self.grow_amortized(used_capacity, needed_extra_capacity, MayMove)
282282
} else {
283283
Ok(())
284284
}
@@ -305,8 +305,7 @@ impl<T, A: AllocRef> RawVec<T, A> {
305305
// This is more readable than putting this in one line:
306306
// `!self.needs_to_grow(...) || self.grow(...).is_ok()`
307307
if self.needs_to_grow(used_capacity, needed_extra_capacity) {
308-
self.grow(Amortized, used_capacity, needed_extra_capacity, InPlace, Uninitialized)
309-
.is_ok()
308+
self.grow_amortized(used_capacity, needed_extra_capacity, InPlace).is_ok()
310309
} else {
311310
true
312311
}
@@ -347,7 +346,7 @@ impl<T, A: AllocRef> RawVec<T, A> {
347346
needed_extra_capacity: usize,
348347
) -> Result<(), TryReserveError> {
349348
if self.needs_to_grow(used_capacity, needed_extra_capacity) {
350-
self.grow(Exact, used_capacity, needed_extra_capacity, MayMove, Uninitialized)
349+
self.grow_exact(used_capacity, needed_extra_capacity)
351350
} else {
352351
Ok(())
353352
}
@@ -372,13 +371,6 @@ impl<T, A: AllocRef> RawVec<T, A> {
372371
}
373372
}
374373

375-
#[derive(Copy, Clone)]
376-
enum Strategy {
377-
Amortized,
378-
Exact,
379-
}
380-
use Strategy::*;
381-
382374
impl<T, A: AllocRef> RawVec<T, A> {
383375
/// Returns if the buffer needs to grow to fulfill the needed extra capacity.
384376
/// Mainly used to make inlining reserve-calls possible without inlining `grow`.
@@ -396,54 +388,59 @@ impl<T, A: AllocRef> RawVec<T, A> {
396388
self.cap = Self::capacity_from_bytes(memory.size);
397389
}
398390

399-
/// Single method to handle all possibilities of growing the buffer.
400-
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(
401399
&mut self,
402-
strategy: Strategy,
403400
used_capacity: usize,
404401
needed_extra_capacity: usize,
405402
placement: ReallocPlacement,
406-
init: AllocInit,
407403
) -> Result<(), TryReserveError> {
408-
let elem_size = mem::size_of::<T>();
409-
if elem_size == 0 {
404+
if mem::size_of::<T>() == 0 {
410405
// Since we return a capacity of `usize::MAX` when `elem_size` is
411406
// 0, getting to here necessarily means the `RawVec` is overfull.
412407
return Err(CapacityOverflow);
413408
}
414-
let new_layout = match strategy {
415-
Amortized => {
416-
// Nothing we can really do about these checks, sadly.
417-
let required_cap =
418-
used_capacity.checked_add(needed_extra_capacity).ok_or(CapacityOverflow)?;
419-
// Cannot overflow, because `cap <= isize::MAX`, and type of `cap` is `usize`.
420-
let double_cap = self.cap * 2;
421-
// `double_cap` guarantees exponential growth.
422-
let cap = cmp::max(double_cap, required_cap);
423-
Layout::array::<T>(cap).map_err(|_| CapacityOverflow)?
424-
}
425-
Exact => {
426-
let cap =
427-
used_capacity.checked_add(needed_extra_capacity).ok_or(CapacityOverflow)?;
428-
Layout::array::<T>(cap).map_err(|_| CapacityOverflow)?
429-
}
430-
};
431-
alloc_guard(new_layout.size())?;
432409

433-
let memory = if let Some((ptr, old_layout)) = self.current_memory() {
434-
debug_assert_eq!(old_layout.align(), new_layout.align());
435-
unsafe {
436-
self.alloc
437-
.grow(ptr, old_layout, new_layout.size(), placement, init)
438-
.map_err(|_| AllocError { layout: new_layout, non_exhaustive: () })?
439-
}
440-
} else {
441-
match placement {
442-
MayMove => self.alloc.alloc(new_layout, init),
443-
InPlace => Err(AllocErr),
444-
}
445-
.map_err(|_| AllocError { layout: new_layout, non_exhaustive: () })?
446-
};
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)?;
447444
self.set_memory(memory);
448445
Ok(())
449446
}
@@ -471,6 +468,38 @@ impl<T, A: AllocRef> RawVec<T, A> {
471468
}
472469
}
473470

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+
474503
impl<T> RawVec<T, Global> {
475504
/// Converts the entire buffer into `Box<[MaybeUninit<T>]>` with the specified `len`.
476505
///

0 commit comments

Comments
 (0)