Skip to content

Commit 272c07f

Browse files
committed
Auto merge of rust-lang#137908 - scottmcm:another-size-hint-attempt, r=<try>
Attempt to use the high part of the `size_hint` in `collect` (again) I last tried something like this [almost 7 years ago](rust-lang#53086); I wonder if it's more tolerable now...
2 parents daf5985 + 76f763c commit 272c07f

File tree

3 files changed

+49
-14
lines changed

3 files changed

+49
-14
lines changed

library/alloc/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@
173173
#![feature(hashmap_internals)]
174174
#![feature(intrinsics)]
175175
#![feature(lang_items)]
176+
#![feature(let_chains)]
176177
#![feature(min_specialization)]
177178
#![feature(multiple_supertrait_upcastable)]
178179
#![feature(negative_impls)]

library/alloc/src/vec/spec_from_iter_nested.rs

+32-14
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,39 @@ where
2222
// empty, but the loop in extend_desugared() is not going to see the
2323
// vector being full in the few subsequent loop iterations.
2424
// So we get better branch prediction.
25-
let mut vector = match iterator.next() {
26-
None => return Vec::new(),
27-
Some(element) => {
28-
let (lower, _) = iterator.size_hint();
29-
let initial_capacity =
30-
cmp::max(RawVec::<T>::MIN_NON_ZERO_CAP, lower.saturating_add(1));
31-
let mut vector = Vec::with_capacity(initial_capacity);
32-
unsafe {
33-
// SAFETY: We requested capacity at least 1
34-
ptr::write(vector.as_mut_ptr(), element);
35-
vector.set_len(1);
36-
}
37-
vector
38-
}
25+
let (low, high) = iterator.size_hint();
26+
let Some(first) = iterator.next() else {
27+
return Vec::new();
3928
};
29+
// `push`'s growth strategy is (currently) to double the capacity if
30+
// there's no space available, so it can have up to 50% "wasted" space.
31+
// Thus if the upper-bound on the size_hint also wouldn't waste more
32+
// than that, just allocate it from the start. (After all, it's silly
33+
// to allocate 254 for a hint of `(254, Some(255)`.)
34+
let initial_capacity = {
35+
// This is written like this to not overflow on any well-behaved iterator,
36+
// even things like `repeat_n(val, isize::MAX as usize + 10)`
37+
// where `low * 2` would need checking.
38+
// A bad (but safe) iterator might have `low > high`, but if so it'll
39+
// produce a huge `extra` that'll probably fail the following check.
40+
let hint = if let Some(high) = high
41+
&& let extra = high - low
42+
&& extra < low
43+
{
44+
high
45+
} else {
46+
low
47+
};
48+
cmp::max(RawVec::<T>::MIN_NON_ZERO_CAP, hint)
49+
};
50+
let mut vector = Vec::with_capacity(initial_capacity);
51+
// SAFETY: We requested capacity at least MIN_NON_ZERO_CAP, which
52+
// is never zero, so there's space for at least one element.
53+
unsafe {
54+
ptr::write(vector.as_mut_ptr(), first);
55+
vector.set_len(1);
56+
}
57+
4058
// must delegate to spec_extend() since extend() itself delegates
4159
// to spec_from for empty Vecs
4260
<Vec<T> as SpecExtend<T, I>>::spec_extend(&mut vector, iterator);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//@ compile-flags: -Copt-level=3
2+
#![crate_type = "lib"]
3+
4+
#[no_mangle]
5+
pub fn should_use_low(a: [i32; 10], b: [i32; 100], p: fn(i32) -> bool) -> Vec<i32> {
6+
// CHECK-LABEL: define void @should_use_low
7+
// CHECK: call{{.+}}dereferenceable_or_null(40){{.+}}@__rust_alloc(
8+
a.iter().copied().chain(b.iter().copied().filter(|x| p(*x))).collect()
9+
}
10+
11+
#[no_mangle]
12+
pub fn should_use_high(a: [i32; 100], b: [i32; 10], p: fn(i32) -> bool) -> Vec<i32> {
13+
// CHECK-LABEL: define void @should_use_high
14+
// CHECK: call{{.+}}dereferenceable_or_null(440){{.+}}@__rust_alloc(
15+
a.iter().copied().chain(b.iter().copied().filter(|x| p(*x))).collect()
16+
}

0 commit comments

Comments
 (0)