Skip to content

Commit 8612aa2

Browse files
committed
Auto merge of #53086 - scottmcm:use-upper-in-collect, r=<try>
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
2 parents b47c314 + fe30b5b commit 8612aa2

File tree

1 file changed

+32
-11
lines changed

1 file changed

+32
-11
lines changed

src/liballoc/vec.rs

+32-11
Original file line numberDiff line numberDiff line change
@@ -1857,19 +1857,40 @@ impl<T, I> SpecExtend<T, I> for Vec<T>
18571857
// empty, but the loop in extend_desugared() is not going to see the
18581858
// vector being full in the few subsequent loop iterations.
18591859
// So we get better branch prediction.
1860-
let mut vector = match iterator.next() {
1861-
None => return Vec::new(),
1862-
Some(element) => {
1863-
let (lower, _) = iterator.size_hint();
1864-
let mut vector = Vec::with_capacity(lower.saturating_add(1));
1865-
unsafe {
1866-
ptr::write(vector.get_unchecked_mut(0), element);
1867-
vector.set_len(1);
1860+
let element =
1861+
if let Some(x) = iterator.next() { x }
1862+
else { return Vec::new() };
1863+
let (lower, upper) = iterator.size_hint();
1864+
let upper = upper.unwrap_or(lower);
1865+
let mut vector =
1866+
if lower >= upper / 2 {
1867+
// This branch covers three main cases:
1868+
// - There was no upper bound, so we just use the lower.
1869+
// - The hint turned out to be exact, so we use it.
1870+
// - Picking the upper won't waste more that the doubling
1871+
// strategy might anyway, so go directly there.
1872+
Vec::with_capacity(upper.saturating_add(1))
1873+
} else {
1874+
// Try to start near the geometric mean of the range. That's
1875+
// never all that high -- even 0B..1GB will only allocate 32kB --
1876+
// but it's much more useful than the lower bound, especially
1877+
// for iterator adapters like filter that have lower == 0.
1878+
let mut v = Vec::new();
1879+
let mag_diff = lower.leading_zeros() - upper.leading_zeros();
1880+
let guess = upper >> (mag_diff / 2);
1881+
match v.try_reserve(guess.saturating_add(1)) {
1882+
Ok(_) => v,
1883+
Err(_) => Vec::with_capacity(lower.saturating_add(1)),
18681884
}
1869-
vector
1870-
}
1871-
};
1885+
};
1886+
unsafe {
1887+
ptr::write(vector.get_unchecked_mut(0), element);
1888+
vector.set_len(1);
1889+
}
18721890
<Vec<T> as SpecExtend<T, I>>::spec_extend(&mut vector, iterator);
1891+
if vector.len() < vector.capacity() / 2 {
1892+
vector.shrink_to_fit();
1893+
}
18731894
vector
18741895
}
18751896

0 commit comments

Comments
 (0)