Skip to content

Commit d853243

Browse files
committed
Optimize ArrayChunks by adding next_chunk_back to DoubleEndedIterator
1 parent ed568e3 commit d853243

3 files changed

Lines changed: 103 additions & 22 deletions

File tree

library/core/src/array/mod.rs

Lines changed: 85 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -964,16 +964,46 @@ impl<T: [const] Destruct> const Drop for Guard<'_, T> {
964964
}
965965
}
966966

967-
/// Pulls `N` items from `iter` and returns them as an array. If the iterator
968-
/// yields fewer than `N` items, `Err` is returned containing an iterator over
969-
/// the already yielded items.
970-
///
971-
/// Since the iterator is passed as a mutable reference and this function calls
972-
/// `next` at most `N` times, the iterator can still be used afterwards to
973-
/// retrieve the remaining items.
974-
///
975-
/// If `iter.next()` panics, all items already yielded by the iterator are
976-
/// dropped.
967+
/// Panic guard for incremental initialization of arrays from the back.
968+
struct GuardBack<'a, T> {
969+
/// The array to be initialized.
970+
pub array_mut: &'a mut [MaybeUninit<T>],
971+
/// The number of items that have been initialized so far.
972+
pub initialized: usize,
973+
}
974+
975+
impl<T> GuardBack<'_, T> {
976+
/// Adds an item to the array and updates the initialized item counter.
977+
///
978+
/// # Safety
979+
///
980+
/// No more than N elements must be initialized.
981+
#[inline]
982+
pub(crate) unsafe fn push_unchecked(&mut self, item: T) {
983+
// SAFETY: If `initialized` was correct before and the caller does not
984+
// invoke this method more than N times, then writes will be in-bounds
985+
// and slots will not be initialized more than once.
986+
unsafe {
987+
self.initialized = self.initialized.unchecked_add(1);
988+
let index = self.array_mut.len().unchecked_sub(self.initialized);
989+
self.array_mut.get_unchecked_mut(index).write(item);
990+
}
991+
}
992+
}
993+
994+
impl<T: Destruct> Drop for GuardBack<'_, T> {
995+
#[inline]
996+
fn drop(&mut self) {
997+
debug_assert!(self.initialized <= self.array_mut.len());
998+
let len = self.array_mut.len();
999+
// SAFETY: this slice will contain only initialized objects.
1000+
unsafe {
1001+
self.array_mut.get_unchecked_mut(len - self.initialized..len).assume_init_drop();
1002+
}
1003+
}
1004+
}
1005+
1006+
/// Pulls `N` items from `iter`.
9771007
///
9781008
/// Used for [`Iterator::next_chunk`].
9791009
#[inline]
@@ -994,11 +1024,7 @@ pub(crate) fn iter_next_chunk<T, const N: usize>(
9941024
}
9951025
}
9961026

997-
/// Version of [`iter_next_chunk`] using a passed-in slice in order to avoid
998-
/// needing to monomorphize for every array length.
999-
///
1000-
/// Unfortunately this loop has two exit conditions, the buffer filling up
1001-
/// or the iterator running out of items, making it tend to optimize poorly.
1027+
/// Version of [`iter_next_chunk`] using a passed-in slice.
10021028
#[inline]
10031029
fn iter_next_chunk_erased<T>(
10041030
buffer: &mut [MaybeUninit<T>],
@@ -1022,3 +1048,47 @@ fn iter_next_chunk_erased<T>(
10221048
mem::forget(guard);
10231049
Ok(())
10241050
}
1051+
1052+
/// Pulls `N` items from the back of `iter`.
1053+
///
1054+
/// Used for [`DoubleEndedIterator::next_chunk_back`].
1055+
#[inline]
1056+
pub(crate) fn iter_next_chunk_back<T, const N: usize>(
1057+
iter: &mut impl DoubleEndedIterator<Item = T>,
1058+
) -> Result<[T; N], IntoIter<T, N>> {
1059+
let mut array = [const { MaybeUninit::uninit() }; N];
1060+
let r = iter_next_chunk_back_erased(&mut array, iter);
1061+
match r {
1062+
Ok(()) => {
1063+
// SAFETY: All elements of `array` were populated.
1064+
Ok(unsafe { MaybeUninit::array_assume_init(array) })
1065+
}
1066+
Err(initialized) => {
1067+
// SAFETY: Only the last `initialized` elements were populated
1068+
Err(unsafe { IntoIter::new_unchecked(array, N - initialized..N) })
1069+
}
1070+
}
1071+
}
1072+
1073+
/// Version of [`iter_next_chunk_back`] using a passed-in slice.
1074+
#[inline]
1075+
fn iter_next_chunk_back_erased<T>(
1076+
buffer: &mut [MaybeUninit<T>],
1077+
iter: &mut impl DoubleEndedIterator<Item = T>,
1078+
) -> Result<(), usize> {
1079+
// if `Iterator::next_back` panics, this guard will drop already initialized items
1080+
let mut guard = GuardBack { array_mut: buffer, initialized: 0 };
1081+
while guard.initialized < guard.array_mut.len() {
1082+
let Some(item) = iter.next_back() else {
1083+
let initialized = guard.initialized;
1084+
mem::forget(guard);
1085+
return Err(initialized);
1086+
};
1087+
1088+
// SAFETY: The loop condition ensures we have space to push the item
1089+
unsafe { guard.push_unchecked(item) };
1090+
}
1091+
1092+
mem::forget(guard);
1093+
Ok(())
1094+
}

library/core/src/iter/adapters/array_chunks.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::array;
22
use crate::iter::adapters::SourceIter;
33
use crate::iter::{
4-
ByRefSized, FusedIterator, InPlaceIterable, TrustedFused, TrustedRandomAccessNoCoerce,
4+
FusedIterator, InPlaceIterable, TrustedFused, TrustedRandomAccessNoCoerce,
55
};
66
use crate::num::NonZero;
77
use crate::ops::{ControlFlow, NeverShortCircuit, Try};
@@ -128,15 +128,11 @@ where
128128
self.next_back_remainder();
129129

130130
let mut acc = init;
131-
let mut iter = ByRefSized(&mut self.iter).rev();
132131

133132
// NB remainder is handled by `next_back_remainder`, so
134-
// `next_chunk` can't return `Err` with non-empty remainder
133+
// `next_chunk_back` can't return `Err` with non-empty remainder
135134
// (assuming correct `I as ExactSizeIterator` impl).
136-
while let Ok(mut chunk) = iter.next_chunk() {
137-
// FIXME: do not do double reverse
138-
// (we could instead add `next_chunk_back` for example)
139-
chunk.reverse();
135+
while let Ok(chunk) = self.iter.next_chunk_back() {
140136
acc = f(acc, chunk)?
141137
}
142138

library/core/src/iter/traits/double_ended.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::array;
12
use crate::num::NonZero;
23
use crate::ops::{ControlFlow, Try};
34

@@ -93,6 +94,20 @@ pub trait DoubleEndedIterator: Iterator {
9394
#[stable(feature = "rust1", since = "1.0.0")]
9495
fn next_back(&mut self) -> Option<Self::Item>;
9596

97+
/// Pulls `N` items from the back of the iterator and returns them as an array.
98+
///
99+
/// See [`Iterator::next_chunk`] for more details.
100+
#[inline]
101+
#[unstable(feature = "iter_next_chunk", issue = "98326")]
102+
fn next_chunk_back<const N: usize>(
103+
&mut self,
104+
) -> Result<[Self::Item; N], array::IntoIter<Self::Item, N>>
105+
where
106+
Self: Sized,
107+
{
108+
crate::array::iter_next_chunk_back(self)
109+
}
110+
96111
/// Advances the iterator from the back by `n` elements.
97112
///
98113
/// `advance_back_by` is the reverse version of [`advance_by`]. This method will

0 commit comments

Comments
 (0)