Skip to content

Commit

Permalink
Auto merge of #53086 - scottmcm:use-upper-in-collect, r=<try>
Browse files Browse the repository at this point in the history
Try to guess a smarter initial capacity in Vec::from_iter

> Another possibility is collect could look at the upper bound and be smarter about what capacity to use?  ~ #45840 (comment)

This is obviously good for hints like `(60, Some(61))` where today we allocate space for 60, then double it should the additional element show up, and it'd be much better to just always allocate 61.

More nuanced are hints like `(0, Some(150))`, where today the code uses just the `0`, and thus starts at a capacity of `1`, but with this change will start at `10` instead.

This can undeniably increase memory pressure over where it is today, so I expect at least some controversy 🙂  It does use `try_reserve` for the allocation that's more than the lower bound, and thus shouldn't introduce new aborts, at least.  And the starting point grows by the root, keeping things fairly contained: even with an hint of `(0, Some(1_000_000_000))` it'll only start at `30_517`.

cc @ljedrz
cc #48994
  • Loading branch information
bors committed Aug 5, 2018
2 parents b47c314 + fe30b5b commit 8612aa2
Showing 1 changed file with 32 additions and 11 deletions.
43 changes: 32 additions & 11 deletions src/liballoc/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1857,19 +1857,40 @@ impl<T, I> SpecExtend<T, I> for Vec<T>
// empty, but the loop in extend_desugared() is not going to see the
// vector being full in the few subsequent loop iterations.
// So we get better branch prediction.
let mut vector = match iterator.next() {
None => return Vec::new(),
Some(element) => {
let (lower, _) = iterator.size_hint();
let mut vector = Vec::with_capacity(lower.saturating_add(1));
unsafe {
ptr::write(vector.get_unchecked_mut(0), element);
vector.set_len(1);
let element =
if let Some(x) = iterator.next() { x }
else { return Vec::new() };
let (lower, upper) = iterator.size_hint();
let upper = upper.unwrap_or(lower);
let mut vector =
if lower >= upper / 2 {
// This branch covers three main cases:
// - There was no upper bound, so we just use the lower.
// - The hint turned out to be exact, so we use it.
// - Picking the upper won't waste more that the doubling
// strategy might anyway, so go directly there.
Vec::with_capacity(upper.saturating_add(1))
} else {
// Try to start near the geometric mean of the range. That's
// never all that high -- even 0B..1GB will only allocate 32kB --
// but it's much more useful than the lower bound, especially
// for iterator adapters like filter that have lower == 0.
let mut v = Vec::new();
let mag_diff = lower.leading_zeros() - upper.leading_zeros();
let guess = upper >> (mag_diff / 2);
match v.try_reserve(guess.saturating_add(1)) {
Ok(_) => v,
Err(_) => Vec::with_capacity(lower.saturating_add(1)),
}
vector
}
};
};
unsafe {
ptr::write(vector.get_unchecked_mut(0), element);
vector.set_len(1);
}
<Vec<T> as SpecExtend<T, I>>::spec_extend(&mut vector, iterator);
if vector.len() < vector.capacity() / 2 {
vector.shrink_to_fit();
}
vector
}

Expand Down

0 comments on commit 8612aa2

Please sign in to comment.