Skip to content

Commit

Permalink
Rollup merge of #103166 - the8472:copied-next-chunk, r=m-ou-se
Browse files Browse the repository at this point in the history
Optimize `slice_iter.copied().next_chunk()`

```
OLD:
test iter::bench_copied_array_chunks                               ... bench:         371 ns/iter (+/- 7)
NEW:
test iter::bench_copied_array_chunks                               ... bench:          31 ns/iter (+/- 0)
```

The default `next_chunk` implementation suffers from having to assemble the array byte by byte via `next()`, checking the `Option<&T>` and then dereferencing `&T`. The specialization copies the chunk directly from the slice.
  • Loading branch information
matthiaskrgr authored Oct 19, 2022
2 parents 2efc90e + 873a18e commit d6eb7bc
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 0 deletions.
20 changes: 20 additions & 0 deletions library/core/benches/iter.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use core::iter::*;
use core::mem;
use core::num::Wrapping;
use test::{black_box, Bencher};

#[bench]
Expand Down Expand Up @@ -398,3 +400,21 @@ fn bench_trusted_random_access_adapters(b: &mut Bencher) {
acc
})
}

/// Exercises the iter::Copied specialization for slice::Iter
#[bench]
fn bench_copied_array_chunks(b: &mut Bencher) {
let v = vec![1u8; 1024];

b.iter(|| {
black_box(&v)
.iter()
.copied()
.array_chunks::<{ mem::size_of::<u64>() }>()
.map(|ary| {
let d = u64::from_ne_bytes(ary);
Wrapping(d.rotate_left(7).wrapping_add(1))
})
.sum::<Wrapping<u64>>()
})
}
1 change: 1 addition & 0 deletions library/core/benches/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#![feature(int_log)]
#![feature(test)]
#![feature(trusted_random_access)]
#![feature(iter_array_chunks)]

extern crate test;

Expand Down
74 changes: 74 additions & 0 deletions library/core/src/iter/adapters/copied.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use crate::iter::adapters::{
zip::try_get_unchecked, TrustedRandomAccess, TrustedRandomAccessNoCoerce,
};
use crate::iter::{FusedIterator, TrustedLen};
use crate::mem::MaybeUninit;
use crate::mem::SizedTypeProperties;
use crate::ops::Try;
use crate::{array, ptr};

/// An iterator that copies the elements of an underlying iterator.
///
Expand Down Expand Up @@ -44,6 +47,15 @@ where
self.it.next().copied()
}

fn next_chunk<const N: usize>(
&mut self,
) -> Result<[Self::Item; N], array::IntoIter<Self::Item, N>>
where
Self: Sized,
{
<I as SpecNextChunk<'_, N, T>>::spec_next_chunk(&mut self.it)
}

fn size_hint(&self) -> (usize, Option<usize>) {
self.it.size_hint()
}
Expand Down Expand Up @@ -166,3 +178,65 @@ where
T: Copy,
{
}

trait SpecNextChunk<'a, const N: usize, T: 'a>: Iterator<Item = &'a T>
where
T: Copy,
{
fn spec_next_chunk(&mut self) -> Result<[T; N], array::IntoIter<T, N>>;
}

impl<'a, const N: usize, I, T: 'a> SpecNextChunk<'a, N, T> for I
where
I: Iterator<Item = &'a T>,
T: Copy,
{
default fn spec_next_chunk(&mut self) -> Result<[T; N], array::IntoIter<T, N>> {
array::iter_next_chunk(&mut self.map(|e| *e))
}
}

impl<'a, const N: usize, T: 'a> SpecNextChunk<'a, N, T> for crate::slice::Iter<'a, T>
where
T: Copy,
{
fn spec_next_chunk(&mut self) -> Result<[T; N], array::IntoIter<T, N>> {
let mut raw_array = MaybeUninit::uninit_array();

let len = self.len();

if T::IS_ZST {
if len < N {
let _ = self.advance_by(len);
// SAFETY: ZSTs can be conjured ex nihilo; only the amount has to be correct
return Err(unsafe { array::IntoIter::new_unchecked(raw_array, 0..len) });
}

let _ = self.advance_by(N);
// SAFETY: ditto
return Ok(unsafe { MaybeUninit::array_assume_init(raw_array) });
}

if len < N {
// SAFETY: `len` indicates that this many elements are available and we just checked that
// it fits into the array.
unsafe {
ptr::copy_nonoverlapping(
self.as_ref().as_ptr(),
raw_array.as_mut_ptr() as *mut T,
len,
);
let _ = self.advance_by(len);
return Err(array::IntoIter::new_unchecked(raw_array, 0..len));
}
}

// SAFETY: `len` is larger than the array size. Copy a fixed amount here to fully initialize
// the array.
unsafe {
ptr::copy_nonoverlapping(self.as_ref().as_ptr(), raw_array.as_mut_ptr() as *mut T, N);
let _ = self.advance_by(N);
Ok(MaybeUninit::array_assume_init(raw_array))
}
}
}

0 comments on commit d6eb7bc

Please sign in to comment.