Skip to content

Commit 0250ff9

Browse files
committed
Auto merge of #22681 - mzabaluev:extend-faster, r=huonw
Instead of a fast branch with a sized iterator falling back to a potentially poorly optimized iterate-and-push loop, a single efficient loop can serve all cases. In my benchmark runs, I see some good gains, but also some regressions, possibly due to different inlining choices by the compiler. YMMV.
2 parents 014a5c1 + 7b464d3 commit 0250ff9

File tree

1 file changed

+39
-39
lines changed

1 file changed

+39
-39
lines changed

src/libcollections/vec.rs

+39-39
Original file line numberDiff line numberDiff line change
@@ -1469,42 +1469,26 @@ impl<T> ops::DerefMut for Vec<T> {
14691469
impl<T> FromIterator<T> for Vec<T> {
14701470
#[inline]
14711471
fn from_iter<I: IntoIterator<Item=T>>(iterable: I) -> Vec<T> {
1472+
// Unroll the first iteration, as the vector is going to be
1473+
// expanded on this iteration in every case when the iterable is not
1474+
// empty, but the loop in extend_desugared() is not going to see the
1475+
// vector being full in the few subsequent loop iterations.
1476+
// So we get better branch prediction and the possibility to
1477+
// construct the vector with initial estimated capacity.
14721478
let mut iterator = iterable.into_iter();
1473-
let (lower, _) = iterator.size_hint();
1474-
let mut vector = Vec::with_capacity(lower);
1475-
1476-
// This function should be the moral equivalent of:
1477-
//
1478-
// for item in iterator {
1479-
// vector.push(item);
1480-
// }
1481-
//
1482-
// This equivalent crucially runs the iterator precisely once. Below we
1483-
// actually in theory run the iterator twice (one without bounds checks
1484-
// and one with). To achieve the "moral equivalent", we use the `if`
1485-
// statement below to break out early.
1486-
//
1487-
// If the first loop has terminated, then we have one of two conditions.
1488-
//
1489-
// 1. The underlying iterator returned `None`. In this case we are
1490-
// guaranteed that less than `vector.capacity()` elements have been
1491-
// returned, so we break out early.
1492-
// 2. The underlying iterator yielded `vector.capacity()` elements and
1493-
// has not yielded `None` yet. In this case we run the iterator to
1494-
// its end below.
1495-
for element in iterator.by_ref().take(vector.capacity()) {
1496-
let len = vector.len();
1497-
unsafe {
1498-
ptr::write(vector.get_unchecked_mut(len), element);
1499-
vector.set_len(len + 1);
1500-
}
1501-
}
1502-
1503-
if vector.len() == vector.capacity() {
1504-
for element in iterator {
1505-
vector.push(element);
1479+
let mut vector = match iterator.next() {
1480+
None => return Vec::new(),
1481+
Some(element) => {
1482+
let (lower, _) = iterator.size_hint();
1483+
let mut vector = Vec::with_capacity(1 + lower);
1484+
unsafe {
1485+
ptr::write(vector.get_unchecked_mut(0), element);
1486+
vector.set_len(1);
1487+
}
1488+
vector
15061489
}
1507-
}
1490+
};
1491+
vector.extend_desugared(iterator);
15081492
vector
15091493
}
15101494
}
@@ -1569,11 +1553,27 @@ impl<'a, T> IntoIterator for &'a mut Vec<T> {
15691553
impl<T> Extend<T> for Vec<T> {
15701554
#[inline]
15711555
fn extend<I: IntoIterator<Item=T>>(&mut self, iterable: I) {
1572-
let iterator = iterable.into_iter();
1573-
let (lower, _) = iterator.size_hint();
1574-
self.reserve(lower);
1575-
for element in iterator {
1576-
self.push(element)
1556+
self.extend_desugared(iterable.into_iter())
1557+
}
1558+
}
1559+
1560+
impl<T> Vec<T> {
1561+
fn extend_desugared<I: Iterator<Item=T>>(&mut self, mut iterator: I) {
1562+
// This function should be the moral equivalent of:
1563+
//
1564+
// for item in iterator {
1565+
// self.push(item);
1566+
// }
1567+
while let Some(element) = iterator.next() {
1568+
let len = self.len();
1569+
if len == self.capacity() {
1570+
let (lower, _) = iterator.size_hint();
1571+
self.reserve(lower + 1);
1572+
}
1573+
unsafe {
1574+
ptr::write(self.get_unchecked_mut(len), element);
1575+
self.set_len(len + 1);
1576+
}
15771577
}
15781578
}
15791579
}

0 commit comments

Comments
 (0)