par_iter/iter/
splitter.rs

1use std::fmt::{self, Debug};
2
3use super::{plumbing::*, *};
4
5/// The `split` function takes arbitrary data and a closure that knows how to
6/// split it, and turns this into a `ParallelIterator`.
7///
8/// # Examples
9///
10/// As a simple example, Rayon can recursively split ranges of indices
11///
12/// ```
13/// use par_iter::iter;
14/// use par_iter::prelude::*;
15/// use std::ops::Range;
16///
17///
18/// // We define a range of indices as follows
19/// type Range1D = Range<usize>;
20///
21/// // Splitting it in two can be done like this
22/// fn split_range1(r: Range1D) -> (Range1D, Option<Range1D>) {
23///     // We are mathematically unable to split the range if there is only
24///     // one point inside of it, but we could stop splitting before that.
25///     if r.end - r.start <= 1 { return (r, None); }
26///
27///     // Here, our range is considered large enough to be splittable
28///     let midpoint = r.start + (r.end - r.start) / 2;
29///     (r.start..midpoint, Some(midpoint..r.end))
30/// }
31///
32/// // By using iter::split, Rayon will split the range until it has enough work
33/// // to feed the CPU cores, then give us the resulting sub-ranges
34/// iter::split(0..4096, split_range1).for_each(|sub_range| {
35///     // As our initial range had a power-of-two size, the final sub-ranges
36///     // should have power-of-two sizes too
37///     assert!((sub_range.end - sub_range.start).is_power_of_two());
38/// });
39/// ```
40///
41/// This recursive splitting can be extended to two or three dimensions,
42/// to reproduce a classic "block-wise" parallelization scheme of graphics and
43/// numerical simulations:
44///
45/// ```
46/// # use par_iter::iter;
47/// # use par_iter::prelude::*;
48/// # use std::ops::Range;
49/// # type Range1D = Range<usize>;
50/// # fn split_range1(r: Range1D) -> (Range1D, Option<Range1D>) {
51/// #     if r.end - r.start <= 1 { return (r, None); }
52/// #     let midpoint = r.start + (r.end - r.start) / 2;
53/// #     (r.start..midpoint, Some(midpoint..r.end))
54/// # }
55/// #
56/// // A two-dimensional range of indices can be built out of two 1D ones
57/// struct Range2D {
58///     // Range of horizontal indices
59///     pub rx: Range1D,
60///
61///     // Range of vertical indices
62///     pub ry: Range1D,
63/// }
64///
65/// // We want to recursively split them by the largest dimension until we have
66/// // enough sub-ranges to feed our mighty multi-core CPU. This function
67/// // carries out one such split.
68/// fn split_range2(r2: Range2D) -> (Range2D, Option<Range2D>) {
69///     // Decide on which axis (horizontal/vertical) the range should be split
70///     let width = r2.rx.end - r2.rx.start;
71///     let height = r2.ry.end - r2.ry.start;
72///     if width >= height {
73///         // This is a wide range, split it on the horizontal axis
74///         let (split_rx, ry) = (split_range1(r2.rx), r2.ry);
75///         let out1 = Range2D {
76///             rx: split_rx.0,
77///             ry: ry.clone(),
78///         };
79///         let out2 = split_rx.1.map(|rx| Range2D { rx, ry });
80///         (out1, out2)
81///     } else {
82///         // This is a tall range, split it on the vertical axis
83///         let (rx, split_ry) = (r2.rx, split_range1(r2.ry));
84///         let out1 = Range2D {
85///             rx: rx.clone(),
86///             ry: split_ry.0,
87///         };
88///         let out2 = split_ry.1.map(|ry| Range2D { rx, ry, });
89///         (out1, out2)
90///     }
91/// }
92///
93/// // Again, rayon can handle the recursive splitting for us
94/// let range = Range2D { rx: 0..800, ry: 0..600 };
95/// iter::split(range, split_range2).for_each(|sub_range| {
96///     // If the sub-ranges were indeed split by the largest dimension, then
97///     // if no dimension was twice larger than the other initially, this
98///     // property will remain true in the final sub-ranges.
99///     let width = sub_range.rx.end - sub_range.rx.start;
100///     let height = sub_range.ry.end - sub_range.ry.start;
101///     assert!((width / 2 <= height) && (height / 2 <= width));
102/// });
103/// ```
104pub fn split<D, S>(data: D, splitter: S) -> Split<D, S>
105where
106    D: Send,
107    S: Fn(D) -> (D, Option<D>) + Sync,
108{
109    Split { data, splitter }
110}
111
112/// `Split` is a parallel iterator using arbitrary data and a splitting
113/// function. This struct is created by the [`split()`] function.
114///
115/// [`split()`]: fn.split.html
116#[derive(Clone)]
117pub struct Split<D, S> {
118    data: D,
119    splitter: S,
120}
121
122impl<D: Debug, S> Debug for Split<D, S> {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124        f.debug_struct("Split").field("data", &self.data).finish()
125    }
126}
127
128impl<D, S> ParallelIterator for Split<D, S>
129where
130    D: Send,
131    S: Fn(D) -> (D, Option<D>) + Sync + Send,
132{
133    type Item = D;
134
135    fn drive_unindexed<C>(self, consumer: C) -> C::Result
136    where
137        C: UnindexedConsumer<Self::Item>,
138    {
139        let producer = SplitProducer {
140            data: self.data,
141            splitter: &self.splitter,
142        };
143        bridge_unindexed(producer, consumer)
144    }
145}
146
147struct SplitProducer<'a, D, S> {
148    data: D,
149    splitter: &'a S,
150}
151
152impl<'a, D, S> UnindexedProducer for SplitProducer<'a, D, S>
153where
154    D: Send,
155    S: Fn(D) -> (D, Option<D>) + Sync,
156{
157    type Item = D;
158
159    fn split(mut self) -> (Self, Option<Self>) {
160        let splitter = self.splitter;
161        let (left, right) = splitter(self.data);
162        self.data = left;
163        (self, right.map(|data| SplitProducer { data, splitter }))
164    }
165
166    fn fold_with<F>(self, folder: F) -> F
167    where
168        F: Folder<Self::Item>,
169    {
170        folder.consume(self.data)
171    }
172}