Skip to content

Commit

Permalink
Merge #629
Browse files Browse the repository at this point in the history
629: Implement chunks_exact and chunks_exact_mut r=cuviper a=zesterer

Follow-up PR from #510. `chunks_exact` (and its mutable counterpart) are not yet implemented on `ParallelIterator`, only `ParallelSlice`.

Co-authored-by: Joshua Barretto <joshua.s.barretto@gmail.com>
Co-authored-by: Josh Stone <cuviper@gmail.com>
  • Loading branch information
3 people authored Mar 12, 2020
2 parents a62b306 + e226a86 commit 3096967
Show file tree
Hide file tree
Showing 5 changed files with 301 additions and 1 deletion.
253 changes: 253 additions & 0 deletions src/slice/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,33 @@ pub trait ParallelSlice<T: Sync> {
slice: self.as_parallel_slice(),
}
}

/// Returns a parallel iterator over `chunk_size` elements of
/// `self` at a time. The chunks do not overlap.
///
/// If `chunk_size` does not divide the length of the slice, then the
/// last up to `chunk_size-1` elements will be omitted and can be
/// retrieved from the remainder function of the iterator.
///
/// # Examples
///
/// ```
/// use rayon::prelude::*;
/// let chunks: Vec<_> = [1, 2, 3, 4, 5].par_chunks_exact(2).collect();
/// assert_eq!(chunks, vec![&[1, 2][..], &[3, 4]]);
/// ```
fn par_chunks_exact(&self, chunk_size: usize) -> ChunksExact<'_, T> {
assert!(chunk_size != 0, "chunk_size must not be zero");
let slice = self.as_parallel_slice();
let rem = slice.len() % chunk_size;
let len = slice.len() - rem;
let (fst, snd) = slice.split_at(len);
ChunksExact {
chunk_size: chunk_size,
slice: fst,
rem: snd,
}
}
}

impl<T: Sync> ParallelSlice<T> for [T] {
Expand Down Expand Up @@ -149,6 +176,35 @@ pub trait ParallelSliceMut<T: Send> {
}
}

/// Returns a parallel iterator over `chunk_size` elements of
/// `self` at a time. The chunks are mutable and do not overlap.
///
/// If `chunk_size` does not divide the length of the slice, then the
/// last up to `chunk_size-1` elements will be omitted and can be
/// retrieved from the remainder function of the iterator.
///
/// # Examples
///
/// ```
/// use rayon::prelude::*;
/// let mut array = [1, 2, 3, 4, 5];
/// array.par_chunks_exact_mut(3)
/// .for_each(|slice| slice.reverse());
/// assert_eq!(array, [3, 2, 1, 4, 5]);
/// ```
fn par_chunks_exact_mut(&mut self, chunk_size: usize) -> ChunksExactMut<'_, T> {
assert!(chunk_size != 0, "chunk_size must not be zero");
let slice = self.as_parallel_slice_mut();
let rem = slice.len() % chunk_size;
let len = slice.len() - rem;
let (fst, snd) = slice.split_at_mut(len);
ChunksExactMut {
chunk_size: chunk_size,
slice: fst,
rem: snd,
}
}

/// Sorts the slice in parallel.
///
/// This sort is stable (i.e. does not reorder equal elements) and `O(n log n)` worst-case.
Expand Down Expand Up @@ -586,6 +642,96 @@ impl<'data, T: 'data + Sync> Producer for ChunksProducer<'data, T> {
}
}

/// Parallel iterator over immutable non-overlapping chunks of a slice
#[derive(Debug)]
pub struct ChunksExact<'data, T: Sync> {
chunk_size: usize,
slice: &'data [T],
rem: &'data [T],
}

impl<'data, T: Sync> ChunksExact<'data, T> {
/// Return the remainder of the original slice that is not going to be
/// returned by the iterator. The returned slice has at most `chunk_size-1`
/// elements.
pub fn remainder(&self) -> &'data [T] {
self.rem
}
}

impl<'data, T: Sync> Clone for ChunksExact<'data, T> {
fn clone(&self) -> Self {
ChunksExact { ..*self }
}
}

impl<'data, T: Sync + 'data> ParallelIterator for ChunksExact<'data, T> {
type Item = &'data [T];

fn drive_unindexed<C>(self, consumer: C) -> C::Result
where
C: UnindexedConsumer<Self::Item>,
{
bridge(self, consumer)
}

fn opt_len(&self) -> Option<usize> {
Some(self.len())
}
}

impl<'data, T: Sync + 'data> IndexedParallelIterator for ChunksExact<'data, T> {
fn drive<C>(self, consumer: C) -> C::Result
where
C: Consumer<Self::Item>,
{
bridge(self, consumer)
}

fn len(&self) -> usize {
self.slice.len() / self.chunk_size
}

fn with_producer<CB>(self, callback: CB) -> CB::Output
where
CB: ProducerCallback<Self::Item>,
{
callback.callback(ChunksExactProducer {
chunk_size: self.chunk_size,
slice: self.slice,
})
}
}

struct ChunksExactProducer<'data, T: Sync> {
chunk_size: usize,
slice: &'data [T],
}

impl<'data, T: 'data + Sync> Producer for ChunksExactProducer<'data, T> {
type Item = &'data [T];
type IntoIter = ::std::slice::ChunksExact<'data, T>;

fn into_iter(self) -> Self::IntoIter {
self.slice.chunks_exact(self.chunk_size)
}

fn split_at(self, index: usize) -> (Self, Self) {
let elem_index = index * self.chunk_size;
let (left, right) = self.slice.split_at(elem_index);
(
ChunksExactProducer {
chunk_size: self.chunk_size,
slice: left,
},
ChunksExactProducer {
chunk_size: self.chunk_size,
slice: right,
},
)
}
}

/// Parallel iterator over immutable overlapping windows of a slice
#[derive(Debug)]
pub struct Windows<'data, T: Sync> {
Expand Down Expand Up @@ -804,6 +950,113 @@ impl<'data, T: 'data + Send> Producer for ChunksMutProducer<'data, T> {
}
}

/// Parallel iterator over mutable non-overlapping chunks of a slice
#[derive(Debug)]
pub struct ChunksExactMut<'data, T: Send> {
chunk_size: usize,
slice: &'data mut [T],
rem: &'data mut [T],
}

impl<'data, T: Send> ChunksExactMut<'data, T> {
/// Return the remainder of the original slice that is not going to be
/// returned by the iterator. The returned slice has at most `chunk_size-1`
/// elements.
///
/// Note that this has to consume `self` to return the original lifetime of
/// the data, which prevents this from actually being used as a parallel
/// iterator since that also consumes. This method is provided for parity
/// with `std::iter::ChunksExactMut`, but consider calling `remainder()` or
/// `take_remainder()` as alternatives.
pub fn into_remainder(self) -> &'data mut [T] {
self.rem
}

/// Return the remainder of the original slice that is not going to be
/// returned by the iterator. The returned slice has at most `chunk_size-1`
/// elements.
///
/// Consider `take_remainder()` if you need access to the data with its
/// original lifetime, rather than borrowing through `&mut self` here.
pub fn remainder(&mut self) -> &mut [T] {
self.rem
}

/// Return the remainder of the original slice that is not going to be
/// returned by the iterator. The returned slice has at most `chunk_size-1`
/// elements. Subsequent calls will return an empty slice.
pub fn take_remainder(&mut self) -> &'data mut [T] {
std::mem::replace(&mut self.rem, &mut [])
}
}

impl<'data, T: Send + 'data> ParallelIterator for ChunksExactMut<'data, T> {
type Item = &'data mut [T];

fn drive_unindexed<C>(self, consumer: C) -> C::Result
where
C: UnindexedConsumer<Self::Item>,
{
bridge(self, consumer)
}

fn opt_len(&self) -> Option<usize> {
Some(self.len())
}
}

impl<'data, T: Send + 'data> IndexedParallelIterator for ChunksExactMut<'data, T> {
fn drive<C>(self, consumer: C) -> C::Result
where
C: Consumer<Self::Item>,
{
bridge(self, consumer)
}

fn len(&self) -> usize {
self.slice.len() / self.chunk_size
}

fn with_producer<CB>(self, callback: CB) -> CB::Output
where
CB: ProducerCallback<Self::Item>,
{
callback.callback(ChunksExactMutProducer {
chunk_size: self.chunk_size,
slice: self.slice,
})
}
}

struct ChunksExactMutProducer<'data, T: Send> {
chunk_size: usize,
slice: &'data mut [T],
}

impl<'data, T: 'data + Send> Producer for ChunksExactMutProducer<'data, T> {
type Item = &'data mut [T];
type IntoIter = ::std::slice::ChunksExactMut<'data, T>;

fn into_iter(self) -> Self::IntoIter {
self.slice.chunks_exact_mut(self.chunk_size)
}

fn split_at(self, index: usize) -> (Self, Self) {
let elem_index = index * self.chunk_size;
let (left, right) = self.slice.split_at_mut(elem_index);
(
ChunksExactMutProducer {
chunk_size: self.chunk_size,
slice: left,
},
ChunksExactMutProducer {
chunk_size: self.chunk_size,
slice: right,
},
)
}
}

/// Parallel iterator over slices separated by a predicate
pub struct Split<'data, T, P> {
slice: &'data [T],
Expand Down
24 changes: 23 additions & 1 deletion src/slice/test.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![cfg(test)]

use super::ParallelSliceMut;
use crate::prelude::*;
use rand::distributions::Uniform;
use rand::seq::SliceRandom;
use rand::{thread_rng, Rng};
Expand Down Expand Up @@ -124,3 +124,25 @@ fn test_par_sort_stability() {
}
}
}

#[test]
fn test_par_chunks_exact_remainder() {
let v: &[i32] = &[0, 1, 2, 3, 4];
let c = v.par_chunks_exact(2);
assert_eq!(c.remainder(), &[4]);
assert_eq!(c.len(), 2);
}

#[test]
fn test_par_chunks_exact_mut_remainder() {
let v: &mut [i32] = &mut [0, 1, 2, 3, 4];
let mut c = v.par_chunks_exact_mut(2);
assert_eq!(c.remainder(), &[4]);
assert_eq!(c.len(), 2);
assert_eq!(c.into_remainder(), &[4]);

let mut c = v.par_chunks_exact_mut(2);
assert_eq!(c.take_remainder(), &[4]);
assert_eq!(c.take_remainder(), &[]);
assert_eq!(c.len(), 2);
}
1 change: 1 addition & 0 deletions tests/clones.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ fn clone_vec() {
let v: Vec<_> = (0..1000).collect();
check(v.par_iter());
check(v.par_chunks(42));
check(v.par_chunks_exact(42));
check(v.par_windows(42));
check(v.par_split(|x| x % 3 == 0));
check(v.into_par_iter());
Expand Down
2 changes: 2 additions & 0 deletions tests/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,9 @@ fn debug_vec() {
check(v.par_iter());
check(v.par_iter_mut());
check(v.par_chunks(42));
check(v.par_chunks_exact(42));
check(v.par_chunks_mut(42));
check(v.par_chunks_exact_mut(42));
check(v.par_windows(42));
check(v.par_split(|x| x % 3 == 0));
check(v.par_split_mut(|x| x % 3 == 0));
Expand Down
22 changes: 22 additions & 0 deletions tests/producer_split_at.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,15 @@ fn slice_chunks() {
}
}

#[test]
fn slice_chunks_exact() {
let s: Vec<_> = (0..10).collect();
for len in 1..s.len() + 2 {
let v: Vec<_> = s.chunks_exact(len).collect();
check(&v, || s.par_chunks_exact(len));
}
}

#[test]
fn slice_chunks_mut() {
let mut s: Vec<_> = (0..10).collect();
Expand All @@ -185,6 +194,19 @@ fn slice_chunks_mut() {
}
}

#[test]
fn slice_chunks_exact_mut() {
let mut s: Vec<_> = (0..10).collect();
let mut v: Vec<_> = s.clone();
for len in 1..s.len() + 2 {
let expected: Vec<_> = v.chunks_exact_mut(len).collect();
map_triples(expected.len() + 1, |i, j, k| {
Split::forward(s.par_chunks_exact_mut(len), i, j, k, &expected);
Split::reverse(s.par_chunks_exact_mut(len), i, j, k, &expected);
});
}
}

#[test]
fn slice_windows() {
let s: Vec<_> = (0..10).collect();
Expand Down

0 comments on commit 3096967

Please sign in to comment.