Skip to content

Commit b40896d

Browse files
committed
optimize next_chunk impls for Filter and FilterMap
1 parent 17a6810 commit b40896d

File tree

3 files changed

+160
-3
lines changed

3 files changed

+160
-3
lines changed

library/core/benches/iter.rs

+44-2
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ fn bench_trusted_random_access_adapters(b: &mut Bencher) {
404404

405405
/// Exercises the iter::Copied specialization for slice::Iter
406406
#[bench]
407-
fn bench_copied_chunks(b: &mut Bencher) {
407+
fn bench_next_chunk_copied(b: &mut Bencher) {
408408
let v = vec![1u8; 1024];
409409

410410
b.iter(|| {
@@ -421,7 +421,7 @@ fn bench_copied_chunks(b: &mut Bencher) {
421421

422422
/// Exercises the TrustedRandomAccess specialization in ArrayChunks
423423
#[bench]
424-
fn bench_trusted_random_access_chunks(b: &mut Bencher) {
424+
fn bench_next_chunk_trusted_random_access(b: &mut Bencher) {
425425
let v = vec![1u8; 1024];
426426

427427
b.iter(|| {
@@ -437,3 +437,45 @@ fn bench_trusted_random_access_chunks(b: &mut Bencher) {
437437
.sum::<Wrapping<u64>>()
438438
})
439439
}
440+
441+
#[bench]
442+
fn bench_next_chunk_filter_even(b: &mut Bencher) {
443+
let a = (0..1024).next_chunk::<1024>().unwrap();
444+
445+
b.iter(|| black_box(&a).iter().filter(|&&i| i % 2 == 0).next_chunk::<32>())
446+
}
447+
448+
#[bench]
449+
fn bench_next_chunk_filter_predictably_true(b: &mut Bencher) {
450+
let a = (0..1024).next_chunk::<1024>().unwrap();
451+
452+
b.iter(|| black_box(&a).iter().filter(|&&i| i < 100).next_chunk::<32>())
453+
}
454+
455+
#[bench]
456+
fn bench_next_chunk_filter_mostly_false(b: &mut Bencher) {
457+
let a = (0..1024).next_chunk::<1024>().unwrap();
458+
459+
b.iter(|| black_box(&a).iter().filter(|&&i| i > 900).next_chunk::<32>())
460+
}
461+
462+
#[bench]
463+
fn bench_next_chunk_filter_map_even(b: &mut Bencher) {
464+
let a = (0..1024).next_chunk::<1024>().unwrap();
465+
466+
b.iter(|| black_box(&a).iter().filter_map(|&i| (i % 2 == 0).then(|| i)).next_chunk::<32>())
467+
}
468+
469+
#[bench]
470+
fn bench_next_chunk_filter_map_predictably_true(b: &mut Bencher) {
471+
let a = (0..1024).next_chunk::<1024>().unwrap();
472+
473+
b.iter(|| black_box(&a).iter().filter_map(|&i| (i < 100).then(|| i)).next_chunk::<32>())
474+
}
475+
476+
#[bench]
477+
fn bench_next_chunk_filter_map_mostly_false(b: &mut Bencher) {
478+
let a = (0..1024).next_chunk::<1024>().unwrap();
479+
480+
b.iter(|| black_box(&a).iter().filter_map(|&i| (i > 900).then(|| i)).next_chunk::<32>())
481+
}

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

+55
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use crate::fmt;
22
use crate::iter::{adapters::SourceIter, FusedIterator, InPlaceIterable};
33
use crate::ops::Try;
4+
use core::array;
5+
use core::mem::{ManuallyDrop, MaybeUninit};
6+
use core::ops::ControlFlow;
47

58
/// An iterator that filters the elements of `iter` with `predicate`.
69
///
@@ -56,6 +59,58 @@ where
5659
self.iter.find(&mut self.predicate)
5760
}
5861

62+
#[inline]
63+
fn next_chunk<const N: usize>(
64+
&mut self,
65+
) -> Result<[Self::Item; N], array::IntoIter<Self::Item, N>> {
66+
let mut array: [MaybeUninit<Self::Item>; N] = MaybeUninit::uninit_array();
67+
68+
struct Guard<'a, T> {
69+
array: &'a mut [MaybeUninit<T>],
70+
initialized: usize,
71+
}
72+
73+
impl<T> Drop for Guard<'_, T> {
74+
#[inline]
75+
fn drop(&mut self) {
76+
if const { crate::mem::needs_drop::<T>() } {
77+
// SAFETY: self.initialized is always <= N, which also is the length of the array.
78+
unsafe {
79+
core::ptr::drop_in_place(MaybeUninit::slice_assume_init_mut(
80+
self.array.get_unchecked_mut(..self.initialized),
81+
));
82+
}
83+
}
84+
}
85+
}
86+
87+
let mut guard = Guard { array: &mut array, initialized: 0 };
88+
89+
let result = self.iter.try_for_each(|element| {
90+
let idx = guard.initialized;
91+
guard.initialized = idx + (self.predicate)(&element) as usize;
92+
93+
// SAFETY: Loop conditions ensure the index is in bounds.
94+
unsafe { guard.array.get_unchecked_mut(idx) }.write(element);
95+
96+
if guard.initialized < N { ControlFlow::Continue(()) } else { ControlFlow::Break(()) }
97+
});
98+
99+
let guard = ManuallyDrop::new(guard);
100+
101+
match result {
102+
ControlFlow::Break(()) => {
103+
// SAFETY: The loop above is only explicitly broken when the array has been fully initialized
104+
Ok(unsafe { MaybeUninit::array_assume_init(array) })
105+
}
106+
ControlFlow::Continue(()) => {
107+
let initialized = guard.initialized;
108+
// SAFETY: The range is in bounds since the loop breaks when reaching N elements.
109+
Err(unsafe { array::IntoIter::new_unchecked(array, 0..initialized) })
110+
}
111+
}
112+
}
113+
59114
#[inline]
60115
fn size_hint(&self) -> (usize, Option<usize>) {
61116
let (_, upper) = self.iter.size_hint();

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

+61-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
use crate::fmt;
21
use crate::iter::{adapters::SourceIter, FusedIterator, InPlaceIterable};
2+
use crate::mem::{ManuallyDrop, MaybeUninit};
33
use crate::ops::{ControlFlow, Try};
4+
use crate::{array, fmt};
45

56
/// An iterator that uses `f` to both filter and map elements from `iter`.
67
///
@@ -61,6 +62,65 @@ where
6162
self.iter.find_map(&mut self.f)
6263
}
6364

65+
#[inline]
66+
fn next_chunk<const N: usize>(
67+
&mut self,
68+
) -> Result<[Self::Item; N], array::IntoIter<Self::Item, N>> {
69+
let mut array: [MaybeUninit<Self::Item>; N] = MaybeUninit::uninit_array();
70+
71+
struct Guard<'a, T> {
72+
array: &'a mut [MaybeUninit<T>],
73+
initialized: usize,
74+
}
75+
76+
impl<T> Drop for Guard<'_, T> {
77+
#[inline]
78+
fn drop(&mut self) {
79+
if const { crate::mem::needs_drop::<T>() } {
80+
// SAFETY: self.initialized is always <= N, which also is the length of the array.
81+
unsafe {
82+
core::ptr::drop_in_place(MaybeUninit::slice_assume_init_mut(
83+
self.array.get_unchecked_mut(..self.initialized),
84+
));
85+
}
86+
}
87+
}
88+
}
89+
90+
let mut guard = Guard { array: &mut array, initialized: 0 };
91+
92+
let result = self.iter.try_for_each(|element| {
93+
let idx = guard.initialized;
94+
let val = (self.f)(element);
95+
guard.initialized = idx + val.is_some() as usize;
96+
97+
// SAFETY: Loop conditions ensure the index is in bounds.
98+
99+
unsafe {
100+
let opt_payload_at = core::intrinsics::option_payload_ptr(&val);
101+
let dst = guard.array.as_mut_ptr().add(idx);
102+
crate::ptr::copy_nonoverlapping(opt_payload_at.cast(), dst, 1);
103+
crate::mem::forget(val);
104+
};
105+
106+
if guard.initialized < N { ControlFlow::Continue(()) } else { ControlFlow::Break(()) }
107+
});
108+
109+
let guard = ManuallyDrop::new(guard);
110+
111+
match result {
112+
ControlFlow::Break(()) => {
113+
// SAFETY: The loop above is only explicitly broken when the array has been fully initialized
114+
Ok(unsafe { MaybeUninit::array_assume_init(array) })
115+
}
116+
ControlFlow::Continue(()) => {
117+
let initialized = guard.initialized;
118+
// SAFETY: The range is in bounds since the loop breaks when reaching N elements.
119+
Err(unsafe { array::IntoIter::new_unchecked(array, 0..initialized) })
120+
}
121+
}
122+
}
123+
64124
#[inline]
65125
fn size_hint(&self) -> (usize, Option<usize>) {
66126
let (_, upper) = self.iter.size_hint();

0 commit comments

Comments
 (0)