@@ -44,14 +44,14 @@ public static void IterateRows<T>(
4444 where T : struct , IRowOperation
4545 {
4646 ValidateRectangle ( rectangle ) ;
47+ ValidateSettings ( parallelSettings ) ;
4748
4849 int top = rectangle . Top ;
4950 int bottom = rectangle . Bottom ;
5051 int width = rectangle . Width ;
5152 int height = rectangle . Height ;
5253
53- int maxSteps = DivideCeil ( width * ( long ) height , parallelSettings . MinimumPixelsProcessedPerTask ) ;
54- int numOfSteps = Math . Min ( parallelSettings . MaxDegreeOfParallelism , maxSteps ) ;
54+ int numOfSteps = GetNumberOfSteps ( width , height , parallelSettings ) ;
5555
5656 // Avoid TPL overhead in this trivial case:
5757 if ( numOfSteps == 1 )
@@ -65,7 +65,7 @@ public static void IterateRows<T>(
6565 }
6666
6767 int verticalStep = DivideCeil ( rectangle . Height , numOfSteps ) ;
68- ParallelOptions parallelOptions = new ( ) { MaxDegreeOfParallelism = numOfSteps } ;
68+ ParallelOptions parallelOptions = CreateParallelOptions ( parallelSettings , numOfSteps ) ;
6969 RowOperationWrapper < T > wrappingOperation = new ( top , bottom , verticalStep , in operation ) ;
7070
7171 _ = Parallel . For (
@@ -109,14 +109,14 @@ public static void IterateRows<T, TBuffer>(
109109 where TBuffer : unmanaged
110110 {
111111 ValidateRectangle ( rectangle ) ;
112+ ValidateSettings ( parallelSettings ) ;
112113
113114 int top = rectangle . Top ;
114115 int bottom = rectangle . Bottom ;
115116 int width = rectangle . Width ;
116117 int height = rectangle . Height ;
117118
118- int maxSteps = DivideCeil ( width * ( long ) height , parallelSettings . MinimumPixelsProcessedPerTask ) ;
119- int numOfSteps = Math . Min ( parallelSettings . MaxDegreeOfParallelism , maxSteps ) ;
119+ int numOfSteps = GetNumberOfSteps ( width , height , parallelSettings ) ;
120120 MemoryAllocator allocator = parallelSettings . MemoryAllocator ;
121121 int bufferLength = Unsafe . AsRef ( in operation ) . GetRequiredBufferLength ( rectangle ) ;
122122
@@ -135,7 +135,7 @@ public static void IterateRows<T, TBuffer>(
135135 }
136136
137137 int verticalStep = DivideCeil ( height , numOfSteps ) ;
138- ParallelOptions parallelOptions = new ( ) { MaxDegreeOfParallelism = numOfSteps } ;
138+ ParallelOptions parallelOptions = CreateParallelOptions ( parallelSettings , numOfSteps ) ;
139139 RowOperationWrapper < T , TBuffer > wrappingOperation = new ( top , bottom , verticalStep , bufferLength , allocator , in operation ) ;
140140
141141 _ = Parallel . For (
@@ -174,14 +174,14 @@ public static void IterateRowIntervals<T>(
174174 where T : struct , IRowIntervalOperation
175175 {
176176 ValidateRectangle ( rectangle ) ;
177+ ValidateSettings ( parallelSettings ) ;
177178
178179 int top = rectangle . Top ;
179180 int bottom = rectangle . Bottom ;
180181 int width = rectangle . Width ;
181182 int height = rectangle . Height ;
182183
183- int maxSteps = DivideCeil ( width * ( long ) height , parallelSettings . MinimumPixelsProcessedPerTask ) ;
184- int numOfSteps = Math . Min ( parallelSettings . MaxDegreeOfParallelism , maxSteps ) ;
184+ int numOfSteps = GetNumberOfSteps ( width , height , parallelSettings ) ;
185185
186186 // Avoid TPL overhead in this trivial case:
187187 if ( numOfSteps == 1 )
@@ -192,7 +192,7 @@ public static void IterateRowIntervals<T>(
192192 }
193193
194194 int verticalStep = DivideCeil ( rectangle . Height , numOfSteps ) ;
195- ParallelOptions parallelOptions = new ( ) { MaxDegreeOfParallelism = numOfSteps } ;
195+ ParallelOptions parallelOptions = CreateParallelOptions ( parallelSettings , numOfSteps ) ;
196196 RowIntervalOperationWrapper < T > wrappingOperation = new ( top , bottom , verticalStep , in operation ) ;
197197
198198 _ = Parallel . For (
@@ -236,14 +236,14 @@ public static void IterateRowIntervals<T, TBuffer>(
236236 where TBuffer : unmanaged
237237 {
238238 ValidateRectangle ( rectangle ) ;
239+ ValidateSettings ( parallelSettings ) ;
239240
240241 int top = rectangle . Top ;
241242 int bottom = rectangle . Bottom ;
242243 int width = rectangle . Width ;
243244 int height = rectangle . Height ;
244245
245- int maxSteps = DivideCeil ( width * ( long ) height , parallelSettings . MinimumPixelsProcessedPerTask ) ;
246- int numOfSteps = Math . Min ( parallelSettings . MaxDegreeOfParallelism , maxSteps ) ;
246+ int numOfSteps = GetNumberOfSteps ( width , height , parallelSettings ) ;
247247 MemoryAllocator allocator = parallelSettings . MemoryAllocator ;
248248 int bufferLength = Unsafe . AsRef ( in operation ) . GetRequiredBufferLength ( rectangle ) ;
249249
@@ -259,7 +259,7 @@ public static void IterateRowIntervals<T, TBuffer>(
259259 }
260260
261261 int verticalStep = DivideCeil ( height , numOfSteps ) ;
262- ParallelOptions parallelOptions = new ( ) { MaxDegreeOfParallelism = numOfSteps } ;
262+ ParallelOptions parallelOptions = CreateParallelOptions ( parallelSettings , numOfSteps ) ;
263263 RowIntervalOperationWrapper < T , TBuffer > wrappingOperation = new ( top , bottom , verticalStep , bufferLength , allocator , in operation ) ;
264264
265265 _ = Parallel . For (
@@ -272,6 +272,37 @@ public static void IterateRowIntervals<T, TBuffer>(
272272 [ MethodImpl ( InliningOptions . ShortMethod ) ]
273273 private static int DivideCeil ( long dividend , int divisor ) => ( int ) Math . Min ( 1 + ( ( dividend - 1 ) / divisor ) , int . MaxValue ) ;
274274
275+ /// <summary>
276+ /// Creates the <see cref="ParallelOptions"/> for the current iteration.
277+ /// </summary>
278+ /// <param name="parallelSettings">The execution settings.</param>
279+ /// <param name="numOfSteps">The number of row partitions to execute.</param>
280+ /// <returns>The <see cref="ParallelOptions"/> instance.</returns>
281+ [ MethodImpl ( InliningOptions . ShortMethod ) ]
282+ private static ParallelOptions CreateParallelOptions ( in ParallelExecutionSettings parallelSettings , int numOfSteps )
283+ => new ( ) { MaxDegreeOfParallelism = parallelSettings . MaxDegreeOfParallelism == - 1 ? - 1 : numOfSteps } ;
284+
285+ /// <summary>
286+ /// Calculates the number of row partitions to execute for the given region.
287+ /// </summary>
288+ /// <param name="width">The width of the region.</param>
289+ /// <param name="height">The height of the region.</param>
290+ /// <param name="parallelSettings">The execution settings.</param>
291+ /// <returns>The number of row partitions to execute.</returns>
292+ [ MethodImpl ( InliningOptions . ShortMethod ) ]
293+ private static int GetNumberOfSteps ( int width , int height , in ParallelExecutionSettings parallelSettings )
294+ {
295+ int maxSteps = DivideCeil ( width * ( long ) height , parallelSettings . MinimumPixelsProcessedPerTask ) ;
296+
297+ if ( parallelSettings . MaxDegreeOfParallelism == - 1 )
298+ {
299+ // Row batching cannot produce more useful partitions than the number of rows available.
300+ return Math . Min ( height , maxSteps ) ;
301+ }
302+
303+ return Math . Min ( parallelSettings . MaxDegreeOfParallelism , maxSteps ) ;
304+ }
305+
275306 private static void ValidateRectangle ( Rectangle rectangle )
276307 {
277308 Guard . MustBeGreaterThan (
@@ -284,4 +315,35 @@ private static void ValidateRectangle(Rectangle rectangle)
284315 0 ,
285316 $ "{ nameof ( rectangle ) } .{ nameof ( rectangle . Height ) } ") ;
286317 }
318+
319+ /// <summary>
320+ /// Validates the supplied <see cref="ParallelExecutionSettings"/>.
321+ /// </summary>
322+ /// <param name="parallelSettings">The execution settings.</param>
323+ /// <exception cref="ArgumentOutOfRangeException">
324+ /// Thrown when <see cref="ParallelExecutionSettings.MaxDegreeOfParallelism"/> or
325+ /// <see cref="ParallelExecutionSettings.MinimumPixelsProcessedPerTask"/> is invalid.
326+ /// </exception>
327+ /// <exception cref="ArgumentNullException">
328+ /// Thrown when <see cref="ParallelExecutionSettings.MemoryAllocator"/> is null.
329+ /// This also guards the public <see cref="ParallelExecutionSettings"/> default value, which bypasses constructor validation.
330+ /// </exception>
331+ private static void ValidateSettings ( in ParallelExecutionSettings parallelSettings )
332+ {
333+ // ParallelExecutionSettings is a public struct, so callers can pass default and bypass constructor validation.
334+ if ( parallelSettings . MaxDegreeOfParallelism is 0 or < - 1 )
335+ {
336+ throw new ArgumentOutOfRangeException (
337+ $ "{ nameof ( parallelSettings ) } .{ nameof ( ParallelExecutionSettings . MaxDegreeOfParallelism ) } ") ;
338+ }
339+
340+ Guard . MustBeGreaterThan (
341+ parallelSettings . MinimumPixelsProcessedPerTask ,
342+ 0 ,
343+ $ "{ nameof ( parallelSettings ) } .{ nameof ( ParallelExecutionSettings . MinimumPixelsProcessedPerTask ) } ") ;
344+
345+ Guard . NotNull (
346+ parallelSettings . MemoryAllocator ,
347+ $ "{ nameof ( parallelSettings ) } .{ nameof ( ParallelExecutionSettings . MemoryAllocator ) } ") ;
348+ }
287349}
0 commit comments