Skip to content

Commit 4507fda

Browse files
committed
Auto merge of #106241 - Sp00ph:vec_deque_iter_methods, r=the8472
Implement more methods for `vec_deque::IntoIter` This implements a couple `Iterator` methods on `vec_deque::IntoIter` (`(try_)fold`, `(try_)rfold` `advance_(back_)by`, `next_chunk`, `count` and `last`) to allow these to be more efficient than their default implementations, also allowing many other `Iterator` methods that use these under the hood to take advantage of these manual implementations. `vec::IntoIter` has similar implementations for many of these methods. This PR does not yet implement `TrustedRandomAccess` and friends, as I'm not very familiar with the required safety guarantees. r? `@the8472` (since you also took over my last PR)
2 parents 53709ae + ccba6c5 commit 4507fda

File tree

2 files changed

+329
-2
lines changed

2 files changed

+329
-2
lines changed

library/alloc/benches/vec_deque.rs

+145-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
use std::collections::VecDeque;
1+
use core::iter::Iterator;
2+
use std::{
3+
collections::{vec_deque, VecDeque},
4+
mem,
5+
};
26
use test::{black_box, Bencher};
37

48
#[bench]
@@ -53,6 +57,146 @@ fn bench_try_fold(b: &mut Bencher) {
5357
b.iter(|| black_box(ring.iter().try_fold(0, |a, b| Some(a + b))))
5458
}
5559

60+
/// does the memory bookkeeping to reuse the buffer of the Vec between iterations.
61+
/// `setup` must not modify its argument's length or capacity. `g` must not move out of its argument.
62+
fn into_iter_helper<
63+
T: Copy,
64+
F: FnOnce(&mut VecDeque<T>),
65+
G: FnOnce(&mut vec_deque::IntoIter<T>),
66+
>(
67+
v: &mut Vec<T>,
68+
setup: F,
69+
g: G,
70+
) {
71+
let ptr = v.as_mut_ptr();
72+
let len = v.len();
73+
// ensure that the vec is full, to make sure that any wrapping from the deque doesn't
74+
// access uninitialized memory.
75+
assert_eq!(v.len(), v.capacity());
76+
77+
let mut deque = VecDeque::from(mem::take(v));
78+
setup(&mut deque);
79+
80+
let mut it = deque.into_iter();
81+
g(&mut it);
82+
83+
mem::forget(it);
84+
85+
// SAFETY: the provided functions are not allowed to modify the allocation, so the buffer is still alive.
86+
// len and capacity are accurate due to the above assertion.
87+
// All the elements in the buffer are still valid, because of `T: Copy` which implies `T: !Drop`.
88+
mem::forget(mem::replace(v, unsafe { Vec::from_raw_parts(ptr, len, len) }));
89+
}
90+
91+
#[bench]
92+
fn bench_into_iter(b: &mut Bencher) {
93+
let len = 1024;
94+
// we reuse this allocation for every run
95+
let mut vec: Vec<usize> = (0..len).collect();
96+
vec.shrink_to_fit();
97+
98+
b.iter(|| {
99+
let mut sum = 0;
100+
into_iter_helper(
101+
&mut vec,
102+
|_| {},
103+
|it| {
104+
for i in it {
105+
sum += i;
106+
}
107+
},
108+
);
109+
black_box(sum);
110+
111+
let mut sum = 0;
112+
// rotating a full deque doesn't move any memory.
113+
into_iter_helper(
114+
&mut vec,
115+
|d| d.rotate_left(len / 2),
116+
|it| {
117+
for i in it {
118+
sum += i;
119+
}
120+
},
121+
);
122+
black_box(sum);
123+
});
124+
}
125+
126+
#[bench]
127+
fn bench_into_iter_fold(b: &mut Bencher) {
128+
let len = 1024;
129+
130+
// because `fold` takes ownership of the iterator,
131+
// we can't prevent it from dropping the memory,
132+
// so we have to bite the bullet and reallocate
133+
// for every iteration.
134+
b.iter(|| {
135+
let deque: VecDeque<usize> = (0..len).collect();
136+
assert_eq!(deque.len(), deque.capacity());
137+
let sum = deque.into_iter().fold(0, |a, b| a + b);
138+
black_box(sum);
139+
140+
// rotating a full deque doesn't move any memory.
141+
let mut deque: VecDeque<usize> = (0..len).collect();
142+
assert_eq!(deque.len(), deque.capacity());
143+
deque.rotate_left(len / 2);
144+
let sum = deque.into_iter().fold(0, |a, b| a + b);
145+
black_box(sum);
146+
});
147+
}
148+
149+
#[bench]
150+
fn bench_into_iter_try_fold(b: &mut Bencher) {
151+
let len = 1024;
152+
// we reuse this allocation for every run
153+
let mut vec: Vec<usize> = (0..len).collect();
154+
vec.shrink_to_fit();
155+
156+
// Iterator::any uses Iterator::try_fold under the hood
157+
b.iter(|| {
158+
let mut b = false;
159+
into_iter_helper(&mut vec, |_| {}, |it| b = it.any(|i| i == len - 1));
160+
black_box(b);
161+
162+
into_iter_helper(&mut vec, |d| d.rotate_left(len / 2), |it| b = it.any(|i| i == len - 1));
163+
black_box(b);
164+
});
165+
}
166+
167+
#[bench]
168+
fn bench_into_iter_next_chunk(b: &mut Bencher) {
169+
let len = 1024;
170+
// we reuse this allocation for every run
171+
let mut vec: Vec<usize> = (0..len).collect();
172+
vec.shrink_to_fit();
173+
174+
b.iter(|| {
175+
let mut buf = [0; 64];
176+
into_iter_helper(
177+
&mut vec,
178+
|_| {},
179+
|it| {
180+
while let Ok(a) = it.next_chunk() {
181+
buf = a;
182+
}
183+
},
184+
);
185+
black_box(buf);
186+
187+
into_iter_helper(
188+
&mut vec,
189+
|d| d.rotate_left(len / 2),
190+
|it| {
191+
while let Ok(a) = it.next_chunk() {
192+
buf = a;
193+
}
194+
},
195+
);
196+
black_box(buf);
197+
});
198+
}
199+
56200
#[bench]
57201
fn bench_from_array_1000(b: &mut Bencher) {
58202
const N: usize = 1000;

library/alloc/src/collections/vec_deque/into_iter.rs

+184-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use core::fmt;
21
use core::iter::{FusedIterator, TrustedLen};
2+
use core::{array, fmt, mem::MaybeUninit, ops::Try, ptr};
33

44
use crate::alloc::{Allocator, Global};
55

@@ -52,6 +52,126 @@ impl<T, A: Allocator> Iterator for IntoIter<T, A> {
5252
let len = self.inner.len();
5353
(len, Some(len))
5454
}
55+
56+
#[inline]
57+
fn advance_by(&mut self, n: usize) -> Result<(), usize> {
58+
if self.inner.len < n {
59+
let len = self.inner.len;
60+
self.inner.clear();
61+
Err(len)
62+
} else {
63+
self.inner.drain(..n);
64+
Ok(())
65+
}
66+
}
67+
68+
#[inline]
69+
fn count(self) -> usize {
70+
self.inner.len
71+
}
72+
73+
fn try_fold<B, F, R>(&mut self, mut init: B, mut f: F) -> R
74+
where
75+
F: FnMut(B, Self::Item) -> R,
76+
R: Try<Output = B>,
77+
{
78+
struct Guard<'a, T, A: Allocator> {
79+
deque: &'a mut VecDeque<T, A>,
80+
// `consumed <= deque.len` always holds.
81+
consumed: usize,
82+
}
83+
84+
impl<'a, T, A: Allocator> Drop for Guard<'a, T, A> {
85+
fn drop(&mut self) {
86+
self.deque.len -= self.consumed;
87+
self.deque.head = self.deque.to_physical_idx(self.consumed);
88+
}
89+
}
90+
91+
let mut guard = Guard { deque: &mut self.inner, consumed: 0 };
92+
93+
let (head, tail) = guard.deque.as_slices();
94+
95+
init = head
96+
.iter()
97+
.map(|elem| {
98+
guard.consumed += 1;
99+
// SAFETY: Because we incremented `guard.consumed`, the
100+
// deque effectively forgot the element, so we can take
101+
// ownership
102+
unsafe { ptr::read(elem) }
103+
})
104+
.try_fold(init, &mut f)?;
105+
106+
tail.iter()
107+
.map(|elem| {
108+
guard.consumed += 1;
109+
// SAFETY: Same as above.
110+
unsafe { ptr::read(elem) }
111+
})
112+
.try_fold(init, &mut f)
113+
}
114+
115+
#[inline]
116+
fn fold<B, F>(mut self, init: B, mut f: F) -> B
117+
where
118+
F: FnMut(B, Self::Item) -> B,
119+
{
120+
match self.try_fold(init, |b, item| Ok::<B, !>(f(b, item))) {
121+
Ok(b) => b,
122+
Err(e) => match e {},
123+
}
124+
}
125+
126+
#[inline]
127+
fn last(mut self) -> Option<Self::Item> {
128+
self.inner.pop_back()
129+
}
130+
131+
fn next_chunk<const N: usize>(
132+
&mut self,
133+
) -> Result<[Self::Item; N], array::IntoIter<Self::Item, N>> {
134+
let mut raw_arr = MaybeUninit::uninit_array();
135+
let raw_arr_ptr = raw_arr.as_mut_ptr().cast();
136+
let (head, tail) = self.inner.as_slices();
137+
138+
if head.len() >= N {
139+
// SAFETY: By manually adjusting the head and length of the deque, we effectively
140+
// make it forget the first `N` elements, so taking ownership of them is safe.
141+
unsafe { ptr::copy_nonoverlapping(head.as_ptr(), raw_arr_ptr, N) };
142+
self.inner.head = self.inner.to_physical_idx(N);
143+
self.inner.len -= N;
144+
// SAFETY: We initialized the entire array with items from `head`
145+
return Ok(unsafe { raw_arr.transpose().assume_init() });
146+
}
147+
148+
// SAFETY: Same argument as above.
149+
unsafe { ptr::copy_nonoverlapping(head.as_ptr(), raw_arr_ptr, head.len()) };
150+
let remaining = N - head.len();
151+
152+
if tail.len() >= remaining {
153+
// SAFETY: Same argument as above.
154+
unsafe {
155+
ptr::copy_nonoverlapping(tail.as_ptr(), raw_arr_ptr.add(head.len()), remaining)
156+
};
157+
self.inner.head = self.inner.to_physical_idx(N);
158+
self.inner.len -= N;
159+
// SAFETY: We initialized the entire array with items from `head` and `tail`
160+
Ok(unsafe { raw_arr.transpose().assume_init() })
161+
} else {
162+
// SAFETY: Same argument as above.
163+
unsafe {
164+
ptr::copy_nonoverlapping(tail.as_ptr(), raw_arr_ptr.add(head.len()), tail.len())
165+
};
166+
let init = head.len() + tail.len();
167+
// We completely drained all the deques elements.
168+
self.inner.head = 0;
169+
self.inner.len = 0;
170+
// SAFETY: We copied all elements from both slices to the beginning of the array, so
171+
// the given range is initialized.
172+
Err(unsafe { array::IntoIter::new_unchecked(raw_arr, 0..init) })
173+
}
174+
}
55175
}
56176

57177
#[stable(feature = "rust1", since = "1.0.0")]
@@ -60,10 +180,73 @@ impl<T, A: Allocator> DoubleEndedIterator for IntoIter<T, A> {
60180
fn next_back(&mut self) -> Option<T> {
61181
self.inner.pop_back()
62182
}
183+
184+
#[inline]
185+
fn advance_back_by(&mut self, n: usize) -> Result<(), usize> {
186+
let len = self.inner.len;
187+
if len >= n {
188+
self.inner.truncate(len - n);
189+
Ok(())
190+
} else {
191+
self.inner.clear();
192+
Err(len)
193+
}
194+
}
195+
196+
fn try_rfold<B, F, R>(&mut self, mut init: B, mut f: F) -> R
197+
where
198+
F: FnMut(B, Self::Item) -> R,
199+
R: Try<Output = B>,
200+
{
201+
struct Guard<'a, T, A: Allocator> {
202+
deque: &'a mut VecDeque<T, A>,
203+
// `consumed <= deque.len` always holds.
204+
consumed: usize,
205+
}
206+
207+
impl<'a, T, A: Allocator> Drop for Guard<'a, T, A> {
208+
fn drop(&mut self) {
209+
self.deque.len -= self.consumed;
210+
}
211+
}
212+
213+
let mut guard = Guard { deque: &mut self.inner, consumed: 0 };
214+
215+
let (head, tail) = guard.deque.as_slices();
216+
217+
init = tail
218+
.iter()
219+
.map(|elem| {
220+
guard.consumed += 1;
221+
// SAFETY: See `try_fold`'s safety comment.
222+
unsafe { ptr::read(elem) }
223+
})
224+
.try_rfold(init, &mut f)?;
225+
226+
head.iter()
227+
.map(|elem| {
228+
guard.consumed += 1;
229+
// SAFETY: Same as above.
230+
unsafe { ptr::read(elem) }
231+
})
232+
.try_rfold(init, &mut f)
233+
}
234+
235+
#[inline]
236+
fn rfold<B, F>(mut self, init: B, mut f: F) -> B
237+
where
238+
F: FnMut(B, Self::Item) -> B,
239+
{
240+
match self.try_rfold(init, |b, item| Ok::<B, !>(f(b, item))) {
241+
Ok(b) => b,
242+
Err(e) => match e {},
243+
}
244+
}
63245
}
64246

65247
#[stable(feature = "rust1", since = "1.0.0")]
66248
impl<T, A: Allocator> ExactSizeIterator for IntoIter<T, A> {
249+
#[inline]
67250
fn is_empty(&self) -> bool {
68251
self.inner.is_empty()
69252
}

0 commit comments

Comments
 (0)