| @@ -9,6 +9,7 @@ use crate::{ | |||||
| copied::Copied, | copied::Copied, | ||||
| filter::Filter, | filter::Filter, | ||||
| filter_map::FilterMap, | filter_map::FilterMap, | ||||
| fold::{Fold, FoldWith}, | |||||
| for_each::ForEach, | for_each::ForEach, | ||||
| inspect::Inspect, | inspect::Inspect, | ||||
| map::Map, | map::Map, | ||||
| @@ -619,6 +620,175 @@ pub trait ParallelIterator<'a>: Sized + Send { | |||||
| TryReduce::new(self, identity, operation) | TryReduce::new(self, identity, operation) | ||||
| } | } | ||||
| /// Parallel fold is similar to sequential fold except that the | |||||
| /// sequence of items may be subdivided before it is | |||||
| /// folded. Consider a list of numbers like `22 3 77 89 46`. If | |||||
| /// you used sequential fold to add them (`fold(0, |a,b| a+b)`, | |||||
| /// you would wind up first adding 0 + 22, then 22 + 3, then 25 + | |||||
| /// 77, and so forth. The **parallel fold** works similarly except | |||||
| /// that it first breaks up your list into sublists, and hence | |||||
| /// instead of yielding up a single sum at the end, it yields up | |||||
| /// multiple sums. The number of results is nondeterministic, as | |||||
| /// is the point where the breaks occur. | |||||
| /// | |||||
| /// So if did the same parallel fold (`fold(0, |a,b| a+b)`) on | |||||
| /// our example list, we might wind up with a sequence of two numbers, | |||||
| /// like so: | |||||
| /// | |||||
| /// ```notrust | |||||
| /// 22 3 77 89 46 | |||||
| /// | | | |||||
| /// 102 135 | |||||
| /// ``` | |||||
| /// | |||||
| /// Or perhaps these three numbers: | |||||
| /// | |||||
| /// ```notrust | |||||
| /// 22 3 77 89 46 | |||||
| /// | | | | |||||
| /// 102 89 46 | |||||
| /// ``` | |||||
| /// | |||||
| /// In general, Rayon will attempt to find good breaking points | |||||
| /// that keep all of your cores busy. | |||||
| /// | |||||
| /// ### Fold versus reduce | |||||
| /// | |||||
| /// The `fold()` and `reduce()` methods each take an identity element | |||||
| /// and a combining function, but they operate rather differently. | |||||
| /// | |||||
| /// `reduce()` requires that the identity function has the same | |||||
| /// type as the things you are iterating over, and it fully | |||||
| /// reduces the list of items into a single item. So, for example, | |||||
| /// imagine we are iterating over a list of bytes `bytes: [128_u8, | |||||
| /// 64_u8, 64_u8]`. If we used `bytes.reduce(|| 0_u8, |a: u8, b: | |||||
| /// u8| a + b)`, we would get an overflow. This is because `0`, | |||||
| /// `a`, and `b` here are all bytes, just like the numbers in the | |||||
| /// list (I wrote the types explicitly above, but those are the | |||||
| /// only types you can use). To avoid the overflow, we would need | |||||
| /// to do something like `bytes.map(|b| b as u32).reduce(|| 0, |a, | |||||
| /// b| a + b)`, in which case our result would be `256`. | |||||
| /// | |||||
| /// In contrast, with `fold()`, the identity function does not | |||||
| /// have to have the same type as the things you are iterating | |||||
| /// over, and you potentially get back many results. So, if we | |||||
| /// continue with the `bytes` example from the previous paragraph, | |||||
| /// we could do `bytes.fold(|| 0_u32, |a, b| a + (b as u32))` to | |||||
| /// convert our bytes into `u32`. And of course we might not get | |||||
| /// back a single sum. | |||||
| /// | |||||
| /// There is a more subtle distinction as well, though it's | |||||
| /// actually implied by the above points. When you use `reduce()`, | |||||
| /// your reduction function is sometimes called with values that | |||||
| /// were never part of your original parallel iterator (for | |||||
| /// example, both the left and right might be a partial sum). With | |||||
| /// `fold()`, in contrast, the left value in the fold function is | |||||
| /// always the accumulator, and the right value is always from | |||||
| /// your original sequence. | |||||
| /// | |||||
| /// ### Fold vs Map/Reduce | |||||
| /// | |||||
| /// Fold makes sense if you have some operation where it is | |||||
| /// cheaper to create groups of elements at a time. For example, | |||||
| /// imagine collecting characters into a string. If you were going | |||||
| /// to use map/reduce, you might try this: | |||||
| /// | |||||
| /// ``` | |||||
| /// use rayon::prelude::*; | |||||
| /// | |||||
| /// let s = | |||||
| /// ['a', 'b', 'c', 'd', 'e'] | |||||
| /// .par_iter() | |||||
| /// .map(|c: &char| format!("{}", c)) | |||||
| /// .reduce(|| String::new(), | |||||
| /// |mut a: String, b: String| { a.push_str(&b); a }); | |||||
| /// | |||||
| /// assert_eq!(s, "abcde"); | |||||
| /// ``` | |||||
| /// | |||||
| /// Because reduce produces the same type of element as its input, | |||||
| /// you have to first map each character into a string, and then | |||||
| /// you can reduce them. This means we create one string per | |||||
| /// element in our iterator -- not so great. Using `fold`, we can | |||||
| /// do this instead: | |||||
| /// | |||||
| /// ``` | |||||
| /// use rayon::prelude::*; | |||||
| /// | |||||
| /// let s = | |||||
| /// ['a', 'b', 'c', 'd', 'e'] | |||||
| /// .par_iter() | |||||
| /// .fold(|| String::new(), | |||||
| /// |mut s: String, c: &char| { s.push(*c); s }) | |||||
| /// .reduce(|| String::new(), | |||||
| /// |mut a: String, b: String| { a.push_str(&b); a }); | |||||
| /// | |||||
| /// assert_eq!(s, "abcde"); | |||||
| /// ``` | |||||
| /// | |||||
| /// Now `fold` will process groups of our characters at a time, | |||||
| /// and we only make one string per group. We should wind up with | |||||
| /// some small-ish number of strings roughly proportional to the | |||||
| /// number of CPUs you have (it will ultimately depend on how busy | |||||
| /// your processors are). Note that we still need to do a reduce | |||||
| /// afterwards to combine those groups of strings into a single | |||||
| /// string. | |||||
| /// | |||||
| /// You could use a similar trick to save partial results (e.g., a | |||||
| /// cache) or something similar. | |||||
| /// | |||||
| /// ### Combining fold with other operations | |||||
| /// | |||||
| /// You can combine `fold` with `reduce` if you want to produce a | |||||
| /// single value. This is then roughly equivalent to a map/reduce | |||||
| /// combination in effect: | |||||
| /// | |||||
| /// ``` | |||||
| /// use rayon::prelude::*; | |||||
| /// | |||||
| /// let bytes = 0..22_u8; | |||||
| /// let sum = bytes.into_par_iter() | |||||
| /// .fold(|| 0_u32, |a: u32, b: u8| a + (b as u32)) | |||||
| /// .sum::<u32>(); | |||||
| /// | |||||
| /// assert_eq!(sum, (0..22).sum()); // compare to sequential | |||||
| /// ``` | |||||
| fn fold<S, O, U>(self, init: S, operation: O) -> Fold<Self, S, O> | |||||
| where | |||||
| S: Fn() -> U + Clone + Send + 'a, | |||||
| O: Fn(U, Self::Item) -> U + Clone + Send + 'a, | |||||
| U: Send, | |||||
| { | |||||
| Fold::new(self, init, operation) | |||||
| } | |||||
| /// Applies `operation` to the given `init` value with each item of this | |||||
| /// iterator, finally producing the value for further use. | |||||
| /// | |||||
| /// This works essentially like `fold(|| init.clone(), operation)`, except | |||||
| /// it doesn't require the `init` type to be `Sync`, nor any other form | |||||
| /// of added synchronization. | |||||
| /// | |||||
| /// # Examples | |||||
| /// | |||||
| /// ``` | |||||
| /// use rayon::prelude::*; | |||||
| /// | |||||
| /// let bytes = 0..22_u8; | |||||
| /// let sum = bytes.into_par_iter() | |||||
| /// .fold_with(0_u32, |a: u32, b: u8| a + (b as u32)) | |||||
| /// .sum::<u32>(); | |||||
| /// | |||||
| /// assert_eq!(sum, (0..22).sum()); // compare to sequential | |||||
| /// ``` | |||||
| fn fold_with<U, O>(self, init: U, operation: O) -> FoldWith<Self, U, O> | |||||
| where | |||||
| U: Clone + Send + 'a, | |||||
| O: Fn(U, Self::Item) -> U + Clone + Send + 'a, | |||||
| { | |||||
| FoldWith::new(self, init, operation) | |||||
| } | |||||
| /// Creates a fresh collection containing all the elements produced | /// Creates a fresh collection containing all the elements produced | ||||
| /// by this parallel iterator. | /// by this parallel iterator. | ||||
| /// | /// | ||||
| @@ -45,10 +45,6 @@ where | |||||
| operation: self.operation, | operation: self.operation, | ||||
| }) | }) | ||||
| } | } | ||||
| fn len_hint_opt(&self) -> Option<usize> { | |||||
| self.base.len_hint_opt() | |||||
| } | |||||
| } | } | ||||
| /* FilterConsumer */ | /* FilterConsumer */ | ||||
| @@ -46,10 +46,6 @@ where | |||||
| operation: self.operation, | operation: self.operation, | ||||
| }) | }) | ||||
| } | } | ||||
| fn len_hint_opt(&self) -> Option<usize> { | |||||
| self.base.len_hint_opt() | |||||
| } | |||||
| } | } | ||||
| /* FilterMapConsumer */ | /* FilterMapConsumer */ | ||||
| @@ -0,0 +1,329 @@ | |||||
| use crate::{Consumer, Executor, Folder, ParallelIterator, Producer, ProducerCallback, Reducer}; | |||||
| /* Fold */ | |||||
| pub struct Fold<X, S, O> { | |||||
| base: X, | |||||
| init: S, | |||||
| operation: O, | |||||
| } | |||||
| impl<X, S, O> Fold<X, S, O> { | |||||
| pub fn new(base: X, init: S, operation: O) -> Self { | |||||
| Self { | |||||
| base, | |||||
| init, | |||||
| operation, | |||||
| } | |||||
| } | |||||
| } | |||||
| impl<'a, X, S, O, U> ParallelIterator<'a> for Fold<X, S, O> | |||||
| where | |||||
| X: ParallelIterator<'a>, | |||||
| S: Fn() -> U + Clone + Send + 'a, | |||||
| O: Fn(U, X::Item) -> U + Clone + Send + 'a, | |||||
| U: Send, | |||||
| { | |||||
| type Item = U; | |||||
| fn drive<E, C, D, R>(self, executor: E, consumer: C) -> E::Result | |||||
| where | |||||
| E: Executor<'a, D>, | |||||
| C: Consumer<Self::Item, Result = D, Reducer = R> + 'a, | |||||
| D: Send, | |||||
| R: Reducer<D> + Send, | |||||
| { | |||||
| self.base.drive( | |||||
| executor, | |||||
| FoldConsumer { | |||||
| base: consumer, | |||||
| init: self.init, | |||||
| operation: self.operation, | |||||
| }, | |||||
| ) | |||||
| } | |||||
| fn with_producer<CB>(self, callback: CB) -> CB::Output | |||||
| where | |||||
| CB: ProducerCallback<'a, Self::Item>, | |||||
| { | |||||
| self.base.with_producer(FoldCallback { | |||||
| base: callback, | |||||
| init: self.init, | |||||
| operation: self.operation, | |||||
| }) | |||||
| } | |||||
| } | |||||
| /* FoldWith */ | |||||
| pub struct FoldWith<X, U, O> { | |||||
| base: X, | |||||
| init: U, | |||||
| operation: O, | |||||
| } | |||||
| impl<X, U, O> FoldWith<X, U, O> { | |||||
| pub fn new(base: X, init: U, operation: O) -> Self { | |||||
| Self { | |||||
| base, | |||||
| init, | |||||
| operation, | |||||
| } | |||||
| } | |||||
| } | |||||
| impl<'a, X, U, O> ParallelIterator<'a> for FoldWith<X, U, O> | |||||
| where | |||||
| X: ParallelIterator<'a>, | |||||
| U: Clone + Send + 'a, | |||||
| O: Fn(U, X::Item) -> U + Clone + Send + 'a, | |||||
| { | |||||
| type Item = U; | |||||
| fn drive<E, C, D, R>(self, executor: E, consumer: C) -> E::Result | |||||
| where | |||||
| E: Executor<'a, D>, | |||||
| C: Consumer<Self::Item, Result = D, Reducer = R> + 'a, | |||||
| D: Send, | |||||
| R: Reducer<D> + Send, | |||||
| { | |||||
| let FoldWith { | |||||
| base, | |||||
| init, | |||||
| operation, | |||||
| } = self; | |||||
| base.drive( | |||||
| executor, | |||||
| FoldConsumer { | |||||
| base: consumer, | |||||
| init: move || init.clone(), | |||||
| operation, | |||||
| }, | |||||
| ) | |||||
| } | |||||
| fn with_producer<CB>(self, callback: CB) -> CB::Output | |||||
| where | |||||
| CB: ProducerCallback<'a, Self::Item>, | |||||
| { | |||||
| let FoldWith { | |||||
| base, | |||||
| init, | |||||
| operation, | |||||
| } = self; | |||||
| base.with_producer(FoldCallback { | |||||
| base: callback, | |||||
| init: move || init.clone(), | |||||
| operation, | |||||
| }) | |||||
| } | |||||
| } | |||||
| /* FoldConsumer */ | |||||
| struct FoldConsumer<C, S, O> { | |||||
| base: C, | |||||
| init: S, | |||||
| operation: O, | |||||
| } | |||||
| impl<'a, C, S, O, T, U> Consumer<T> for FoldConsumer<C, S, O> | |||||
| where | |||||
| C: Consumer<U>, | |||||
| S: Fn() -> U + Clone + Send, | |||||
| O: Fn(U, T) -> U + Clone + Send, | |||||
| { | |||||
| type Folder = FoldFolder<C::Folder, O, U>; | |||||
| type Reducer = C::Reducer; | |||||
| type Result = C::Result; | |||||
| fn split(self) -> (Self, Self, Self::Reducer) { | |||||
| let (left, right, reducer) = self.base.split(); | |||||
| let left = FoldConsumer { | |||||
| base: left, | |||||
| init: self.init.clone(), | |||||
| operation: self.operation.clone(), | |||||
| }; | |||||
| let right = FoldConsumer { | |||||
| base: right, | |||||
| init: self.init, | |||||
| operation: self.operation, | |||||
| }; | |||||
| (left, right, reducer) | |||||
| } | |||||
| fn split_at(self, index: usize) -> (Self, Self, Self::Reducer) { | |||||
| let (left, right, reducer) = self.base.split_at(index); | |||||
| let left = FoldConsumer { | |||||
| base: left, | |||||
| init: self.init.clone(), | |||||
| operation: self.operation.clone(), | |||||
| }; | |||||
| let right = FoldConsumer { | |||||
| base: right, | |||||
| init: self.init, | |||||
| operation: self.operation, | |||||
| }; | |||||
| (left, right, reducer) | |||||
| } | |||||
| fn into_folder(self) -> Self::Folder { | |||||
| FoldFolder { | |||||
| base: self.base.into_folder(), | |||||
| item: (self.init)(), | |||||
| operation: self.operation, | |||||
| } | |||||
| } | |||||
| fn is_full(&self) -> bool { | |||||
| self.base.is_full() | |||||
| } | |||||
| } | |||||
| /* FoldCallback */ | |||||
| struct FoldCallback<CB, S, O> { | |||||
| base: CB, | |||||
| init: S, | |||||
| operation: O, | |||||
| } | |||||
| impl<'a, CB, S, O, T, U> ProducerCallback<'a, T> for FoldCallback<CB, S, O> | |||||
| where | |||||
| CB: ProducerCallback<'a, U>, | |||||
| S: Fn() -> U + Clone + Send + 'a, | |||||
| O: Fn(U, T) -> U + Clone + Send + 'a, | |||||
| { | |||||
| type Output = CB::Output; | |||||
| fn callback<P>(self, producer: P) -> Self::Output | |||||
| where | |||||
| P: Producer<Item = T> + 'a, | |||||
| { | |||||
| self.base.callback(FoldProducer { | |||||
| base: producer, | |||||
| init: self.init, | |||||
| operation: self.operation, | |||||
| }) | |||||
| } | |||||
| } | |||||
| /* FoldProducer */ | |||||
| struct FoldProducer<P, S, O> { | |||||
| base: P, | |||||
| init: S, | |||||
| operation: O, | |||||
| } | |||||
| impl<'a, P, S, O, U> Producer for FoldProducer<P, S, O> | |||||
| where | |||||
| P: Producer, | |||||
| S: Fn() -> U + Clone + Send, | |||||
| O: Fn(U, P::Item) -> U + Clone + Send, | |||||
| { | |||||
| type Item = U; | |||||
| type IntoIter = std::iter::Once<U>; | |||||
| fn into_iter(self) -> Self::IntoIter { | |||||
| let item = (self.init)(); | |||||
| let item = self.base.into_iter().fold(item, self.operation); | |||||
| std::iter::once(item) | |||||
| } | |||||
| fn split(self) -> (Self, Option<Self>) { | |||||
| let init = self.init; | |||||
| let operation = self.operation; | |||||
| let (left, right) = self.base.split(); | |||||
| let left = FoldProducer { | |||||
| base: left, | |||||
| init: init.clone(), | |||||
| operation: operation.clone(), | |||||
| }; | |||||
| let right = right.map(move |right| FoldProducer { | |||||
| base: right, | |||||
| init, | |||||
| operation, | |||||
| }); | |||||
| (left, right) | |||||
| } | |||||
| fn splits(&self) -> Option<usize> { | |||||
| self.base.splits() | |||||
| } | |||||
| fn fold_with<F>(self, folder: F) -> F | |||||
| where | |||||
| F: Folder<Self::Item>, | |||||
| { | |||||
| self.base | |||||
| .fold_with(FoldFolder { | |||||
| base: folder, | |||||
| item: (self.init)(), | |||||
| operation: self.operation, | |||||
| }) | |||||
| .base | |||||
| } | |||||
| } | |||||
| /* FoldFolder */ | |||||
| struct FoldFolder<F, O, U> { | |||||
| base: F, | |||||
| operation: O, | |||||
| item: U, | |||||
| } | |||||
| impl<F, O, U, T> Folder<T> for FoldFolder<F, O, U> | |||||
| where | |||||
| F: Folder<U>, | |||||
| O: Fn(U, T) -> U + Clone, | |||||
| { | |||||
| type Result = F::Result; | |||||
| fn consume(mut self, item: T) -> Self { | |||||
| self.item = (self.operation)(self.item, item); | |||||
| self | |||||
| } | |||||
| fn consume_iter<X>(self, iter: X) -> Self | |||||
| where | |||||
| X: IntoIterator<Item = T>, | |||||
| { | |||||
| let FoldFolder { | |||||
| base, | |||||
| operation, | |||||
| item, | |||||
| } = self; | |||||
| let item = iter | |||||
| .into_iter() | |||||
| .take_while(|_| !base.is_full()) | |||||
| .fold(item, operation.clone()); | |||||
| FoldFolder { | |||||
| base, | |||||
| operation, | |||||
| item, | |||||
| } | |||||
| } | |||||
| fn complete(self) -> Self::Result { | |||||
| self.base.consume(self.item).complete() | |||||
| } | |||||
| fn is_full(&self) -> bool { | |||||
| self.base.is_full() | |||||
| } | |||||
| } | |||||
| @@ -3,6 +3,7 @@ pub mod collect; | |||||
| pub mod copied; | pub mod copied; | ||||
| pub mod filter; | pub mod filter; | ||||
| pub mod filter_map; | pub mod filter_map; | ||||
| pub mod fold; | |||||
| pub mod for_each; | pub mod for_each; | ||||
| pub mod inspect; | pub mod inspect; | ||||
| pub mod map; | pub mod map; | ||||
| @@ -27,23 +28,20 @@ mod tests { | |||||
| let j = Arc::new(AtomicUsize::new(0)); | let j = Arc::new(AtomicUsize::new(0)); | ||||
| let x = vec![ | let x = vec![ | ||||
| vec![0usize], | |||||
| vec![1usize], | |||||
| vec![2usize], | |||||
| vec![3usize], | |||||
| vec![4usize], | |||||
| vec![5usize], | |||||
| vec![1usize, 2usize], | |||||
| vec![3usize, 4usize], | |||||
| vec![5usize, 6usize], | |||||
| ]; | ]; | ||||
| let x = x | let x = x | ||||
| .par_iter() | .par_iter() | ||||
| .cloned() | .cloned() | ||||
| .update(|x| x.push(5)) | |||||
| .update(|x| x.push(0)) | |||||
| .map_init( | .map_init( | ||||
| move || i.fetch_add(1, Ordering::Relaxed), | move || i.fetch_add(1, Ordering::Relaxed), | ||||
| |init, item| (item, *init), | |init, item| (item, *init), | ||||
| ) | ) | ||||
| .filter_map(|(x, i)| if i % 2 == 0 { Some((i, x)) } else { None }) | |||||
| .fold_with(String::new(), |s, item| format!("{} + {:?}", s, item)) | |||||
| .try_for_each_init( | .try_for_each_init( | ||||
| move || j.fetch_add(1, Ordering::Relaxed), | move || j.fetch_add(1, Ordering::Relaxed), | ||||
| |init, item| -> Result<(), ()> { | |init, item| -> Result<(), ()> { | ||||