@@ -2,6 +2,8 @@ use crate::{Array, ArrayBase, DataMut, Dimension, IntoNdProducer, NdProducer, Zi
22use crate :: AssignElem ;
33
44use crate :: parallel:: prelude:: * ;
5+ use crate :: parallel:: par:: ParallelSplits ;
6+ use super :: send_producer:: SendProducer ;
57
68/// # Parallel methods
79///
4345
4446// Zip
4547
48+ const COLLECT_MAX_PARTS : usize = 256 ;
49+
4650macro_rules! zip_impl {
4751 ( $( [ $notlast: ident $( $p: ident) * ] , ) +) => {
4852 $(
@@ -71,14 +75,56 @@ macro_rules! zip_impl {
7175 /// inputs.
7276 ///
7377 /// If all inputs are c- or f-order respectively, that is preserved in the output.
74- ///
75- /// Restricted to functions that produce copyable results for technical reasons; other
76- /// cases are not yet implemented.
77- pub fn par_apply_collect<R >( self , f: impl Fn ( $( $p:: Item , ) * ) -> R + Sync + Send ) -> Array <R , D >
78- where R : Copy + Send
78+ pub fn par_apply_collect<R >( self , f: impl Fn ( $( $p:: Item , ) * ) -> R + Sync + Send )
79+ -> Array <R , D >
80+ where R : Send
7981 {
8082 let mut output = self . uninitalized_for_current_layout:: <R >( ) ;
81- self . par_apply_assign_into( & mut output, f) ;
83+ let total_len = output. len( ) ;
84+
85+ // Create a parallel iterator that produces chunks of the zip with the output
86+ // array. It's crucial that both parts split in the same way, and in a way
87+ // so that the chunks of the output are still contig.
88+ //
89+ // Use a raw view so that we can alias the output data here and in the partial
90+ // result.
91+ let splits = unsafe {
92+ ParallelSplits {
93+ iter: self . and( SendProducer :: new( output. raw_view_mut( ) . cast:: <R >( ) ) ) ,
94+ // Keep it from splitting the Zip down too small
95+ min_size: total_len / COLLECT_MAX_PARTS ,
96+ }
97+ } ;
98+
99+ let collect_result = splits. map( move |zip| {
100+ // Create a partial result for the contiguous slice of data being written to
101+ let output = zip. last_producer( ) ;
102+ debug_assert!( output. is_contiguous( ) ) ;
103+
104+ let mut partial = Partial :: new( output. as_ptr( ) ) ;
105+
106+ // Apply the mapping function on this chunk of the zip
107+ let partial_len = & mut partial. len;
108+ let f = & f;
109+ zip. apply( move |$( $p, ) * output_elem: * mut R | unsafe {
110+ output_elem. write( f( $( $p) ,* ) ) ;
111+ if std:: mem:: needs_drop:: <R >( ) {
112+ * partial_len += 1 ;
113+ }
114+ } ) ;
115+
116+ partial
117+ } )
118+ . reduce( Partial :: stub, Partial :: try_merge) ;
119+
120+ if std:: mem:: needs_drop:: <R >( ) {
121+ debug_assert_eq!( total_len, collect_result. len, "collect len is not correct, expected {}" , total_len) ;
122+ assert!( collect_result. len == total_len, "Collect: Expected number of writes not completed" ) ;
123+ }
124+
125+ // Here the collect result is complete, and we release its ownership and transfer
126+ // it to the output array.
127+ collect_result. release_ownership( ) ;
82128 unsafe {
83129 output. assume_init( )
84130 }
@@ -113,3 +159,71 @@ zip_impl! {
113159 [ true P1 P2 P3 P4 P5 ] ,
114160 [ false P1 P2 P3 P4 P5 P6 ] ,
115161}
162+
163+ /// Partial is a partially written contiguous slice of data;
164+ /// it is the owner of the elements, but not the allocation,
165+ /// and will drop the elements on drop.
166+ #[ must_use]
167+ pub ( crate ) struct Partial < T > {
168+ /// Data pointer
169+ ptr : * mut T ,
170+ /// Current length
171+ len : usize ,
172+ }
173+
174+ impl < T > Partial < T > {
175+ /// Create an empty partial for this data pointer
176+ pub ( crate ) fn new ( ptr : * mut T ) -> Self {
177+ Self {
178+ ptr,
179+ len : 0 ,
180+ }
181+ }
182+
183+ pub ( crate ) fn stub ( ) -> Self {
184+ Self { len : 0 , ptr : 0 as * mut _ }
185+ }
186+
187+ pub ( crate ) fn is_stub ( & self ) -> bool {
188+ self . ptr . is_null ( )
189+ }
190+
191+ /// Release Partial's ownership of the written elements, and return the current length
192+ pub ( crate ) fn release_ownership ( mut self ) -> usize {
193+ let ret = self . len ;
194+ self . len = 0 ;
195+ ret
196+ }
197+
198+ /// Merge if they are in order (left to right) and contiguous.
199+ /// Skips merge if T does not need drop.
200+ pub ( crate ) fn try_merge ( mut left : Self , right : Self ) -> Self {
201+ if !std:: mem:: needs_drop :: < T > ( ) {
202+ return left;
203+ }
204+ // Merge the partial collect results; the final result will be a slice that
205+ // covers the whole output.
206+ if left. is_stub ( ) {
207+ right
208+ } else if left. ptr . wrapping_add ( left. len ) == right. ptr {
209+ left. len += right. release_ownership ( ) ;
210+ left
211+ } else {
212+ // failure to merge; this is a bug in collect, so we will never reach this
213+ debug_assert ! ( false , "Partial: failure to merge left and right parts" ) ;
214+ left
215+ }
216+ }
217+ }
218+
219+ unsafe impl < T > Send for Partial < T > where T : Send { }
220+
221+ impl < T > Drop for Partial < T > {
222+ fn drop ( & mut self ) {
223+ if !self . ptr . is_null ( ) {
224+ unsafe {
225+ std:: ptr:: drop_in_place ( std:: slice:: from_raw_parts_mut ( self . ptr , self . len ) ) ;
226+ }
227+ }
228+ }
229+ }
0 commit comments