-
-
Notifications
You must be signed in to change notification settings - Fork 893
Allow -1 (unbounded) parallelism; validate settings #3110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -44,14 +44,14 @@ public static void IterateRows<T>( | |
| where T : struct, IRowOperation | ||
| { | ||
| ValidateRectangle(rectangle); | ||
| ValidateSettings(parallelSettings); | ||
|
|
||
| int top = rectangle.Top; | ||
| int bottom = rectangle.Bottom; | ||
| int width = rectangle.Width; | ||
| int height = rectangle.Height; | ||
|
|
||
| int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask); | ||
| int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); | ||
| int numOfSteps = GetNumberOfSteps(width, height, parallelSettings); | ||
|
|
||
| // Avoid TPL overhead in this trivial case: | ||
| if (numOfSteps == 1) | ||
|
|
@@ -65,7 +65,7 @@ public static void IterateRows<T>( | |
| } | ||
|
|
||
| int verticalStep = DivideCeil(rectangle.Height, numOfSteps); | ||
| ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps }; | ||
| ParallelOptions parallelOptions = CreateParallelOptions(parallelSettings, numOfSteps); | ||
| RowOperationWrapper<T> wrappingOperation = new(top, bottom, verticalStep, in operation); | ||
|
|
||
| _ = Parallel.For( | ||
|
|
@@ -109,14 +109,14 @@ public static void IterateRows<T, TBuffer>( | |
| where TBuffer : unmanaged | ||
| { | ||
| ValidateRectangle(rectangle); | ||
| ValidateSettings(parallelSettings); | ||
|
|
||
| int top = rectangle.Top; | ||
| int bottom = rectangle.Bottom; | ||
| int width = rectangle.Width; | ||
| int height = rectangle.Height; | ||
|
|
||
| int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask); | ||
| int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); | ||
| int numOfSteps = GetNumberOfSteps(width, height, parallelSettings); | ||
| MemoryAllocator allocator = parallelSettings.MemoryAllocator; | ||
| int bufferLength = Unsafe.AsRef(in operation).GetRequiredBufferLength(rectangle); | ||
|
|
||
|
|
@@ -135,7 +135,7 @@ public static void IterateRows<T, TBuffer>( | |
| } | ||
|
|
||
| int verticalStep = DivideCeil(height, numOfSteps); | ||
| ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps }; | ||
| ParallelOptions parallelOptions = CreateParallelOptions(parallelSettings, numOfSteps); | ||
| RowOperationWrapper<T, TBuffer> wrappingOperation = new(top, bottom, verticalStep, bufferLength, allocator, in operation); | ||
|
|
||
| _ = Parallel.For( | ||
|
|
@@ -174,14 +174,14 @@ public static void IterateRowIntervals<T>( | |
| where T : struct, IRowIntervalOperation | ||
| { | ||
| ValidateRectangle(rectangle); | ||
| ValidateSettings(parallelSettings); | ||
|
|
||
| int top = rectangle.Top; | ||
| int bottom = rectangle.Bottom; | ||
| int width = rectangle.Width; | ||
| int height = rectangle.Height; | ||
|
|
||
| int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask); | ||
| int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); | ||
| int numOfSteps = GetNumberOfSteps(width, height, parallelSettings); | ||
|
|
||
| // Avoid TPL overhead in this trivial case: | ||
| if (numOfSteps == 1) | ||
|
|
@@ -192,7 +192,7 @@ public static void IterateRowIntervals<T>( | |
| } | ||
|
|
||
| int verticalStep = DivideCeil(rectangle.Height, numOfSteps); | ||
| ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps }; | ||
| ParallelOptions parallelOptions = CreateParallelOptions(parallelSettings, numOfSteps); | ||
| RowIntervalOperationWrapper<T> wrappingOperation = new(top, bottom, verticalStep, in operation); | ||
|
|
||
| _ = Parallel.For( | ||
|
|
@@ -236,14 +236,14 @@ public static void IterateRowIntervals<T, TBuffer>( | |
| where TBuffer : unmanaged | ||
| { | ||
| ValidateRectangle(rectangle); | ||
| ValidateSettings(parallelSettings); | ||
|
|
||
| int top = rectangle.Top; | ||
| int bottom = rectangle.Bottom; | ||
| int width = rectangle.Width; | ||
| int height = rectangle.Height; | ||
|
|
||
| int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask); | ||
| int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); | ||
| int numOfSteps = GetNumberOfSteps(width, height, parallelSettings); | ||
| MemoryAllocator allocator = parallelSettings.MemoryAllocator; | ||
| int bufferLength = Unsafe.AsRef(in operation).GetRequiredBufferLength(rectangle); | ||
|
|
||
|
|
@@ -259,7 +259,7 @@ public static void IterateRowIntervals<T, TBuffer>( | |
| } | ||
|
|
||
| int verticalStep = DivideCeil(height, numOfSteps); | ||
| ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps }; | ||
| ParallelOptions parallelOptions = CreateParallelOptions(parallelSettings, numOfSteps); | ||
| RowIntervalOperationWrapper<T, TBuffer> wrappingOperation = new(top, bottom, verticalStep, bufferLength, allocator, in operation); | ||
|
|
||
| _ = Parallel.For( | ||
|
|
@@ -272,6 +272,37 @@ public static void IterateRowIntervals<T, TBuffer>( | |
| [MethodImpl(InliningOptions.ShortMethod)] | ||
| private static int DivideCeil(long dividend, int divisor) => (int)Math.Min(1 + ((dividend - 1) / divisor), int.MaxValue); | ||
|
|
||
| /// <summary> | ||
| /// Creates the <see cref="ParallelOptions"/> for the current iteration. | ||
| /// </summary> | ||
| /// <param name="parallelSettings">The execution settings.</param> | ||
| /// <param name="numOfSteps">The number of row partitions to execute.</param> | ||
| /// <returns>The <see cref="ParallelOptions"/> instance.</returns> | ||
| [MethodImpl(InliningOptions.ShortMethod)] | ||
| private static ParallelOptions CreateParallelOptions(in ParallelExecutionSettings parallelSettings, int numOfSteps) | ||
| => new() { MaxDegreeOfParallelism = parallelSettings.MaxDegreeOfParallelism == -1 ? -1 : numOfSteps }; | ||
|
|
||
| /// <summary> | ||
| /// Calculates the number of row partitions to execute for the given region. | ||
| /// </summary> | ||
| /// <param name="width">The width of the region.</param> | ||
| /// <param name="height">The height of the region.</param> | ||
| /// <param name="parallelSettings">The execution settings.</param> | ||
| /// <returns>The number of row partitions to execute.</returns> | ||
| [MethodImpl(InliningOptions.ShortMethod)] | ||
| private static int GetNumberOfSteps(int width, int height, in ParallelExecutionSettings parallelSettings) | ||
| { | ||
| int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask); | ||
|
|
||
| if (parallelSettings.MaxDegreeOfParallelism == -1) | ||
| { | ||
| // Row batching cannot produce more useful partitions than the number of rows available. | ||
| return Math.Min(height, maxSteps); | ||
| } | ||
|
Comment on lines
+297
to
+301
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Edit: Sorry, never mind, it handles the MaxDegreeOfParallelism == -1 case correctly. |
||
|
|
||
| return Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); | ||
| } | ||
|
|
||
| private static void ValidateRectangle(Rectangle rectangle) | ||
| { | ||
| Guard.MustBeGreaterThan( | ||
|
|
@@ -284,4 +315,35 @@ private static void ValidateRectangle(Rectangle rectangle) | |
| 0, | ||
| $"{nameof(rectangle)}.{nameof(rectangle.Height)}"); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Validates the supplied <see cref="ParallelExecutionSettings"/>. | ||
| /// </summary> | ||
| /// <param name="parallelSettings">The execution settings.</param> | ||
| /// <exception cref="ArgumentOutOfRangeException"> | ||
| /// Thrown when <see cref="ParallelExecutionSettings.MaxDegreeOfParallelism"/> or | ||
| /// <see cref="ParallelExecutionSettings.MinimumPixelsProcessedPerTask"/> is invalid. | ||
| /// </exception> | ||
| /// <exception cref="ArgumentNullException"> | ||
| /// Thrown when <see cref="ParallelExecutionSettings.MemoryAllocator"/> is null. | ||
| /// This also guards the public <see cref="ParallelExecutionSettings"/> default value, which bypasses constructor validation. | ||
| /// </exception> | ||
| private static void ValidateSettings(in ParallelExecutionSettings parallelSettings) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't actually need it. I had it in my head the struct was in the |
||
| { | ||
| // ParallelExecutionSettings is a public struct, so callers can pass default and bypass constructor validation. | ||
| if (parallelSettings.MaxDegreeOfParallelism is 0 or < -1) | ||
| { | ||
| throw new ArgumentOutOfRangeException( | ||
| $"{nameof(parallelSettings)}.{nameof(ParallelExecutionSettings.MaxDegreeOfParallelism)}"); | ||
| } | ||
|
|
||
| Guard.MustBeGreaterThan( | ||
| parallelSettings.MinimumPixelsProcessedPerTask, | ||
| 0, | ||
| $"{nameof(parallelSettings)}.{nameof(ParallelExecutionSettings.MinimumPixelsProcessedPerTask)}"); | ||
|
|
||
| Guard.NotNull( | ||
| parallelSettings.MemoryAllocator, | ||
| $"{nameof(parallelSettings)}.{nameof(ParallelExecutionSettings.MemoryAllocator)}"); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JimBobSquarePants first I didn't want to comment because it felt like nitpicking on terminology, but now I'm afraid the proposed semantics for
-1are odd.The official docs do not say -1 means unbounded, they only state that
This doesn't mean that there will be no reasonable bounds on the number of operations, it means that the ThreadPool will dynamically determine the number.
https://github.com/SixLabors/ImageSharp.Drawing/blob/a4d03787dfc5a18cfe106d13b907a20d31534a8d/src/ImageSharp.Drawing/Processing/Backends/ParallelExecutionHelper.cs#L21-L22
However, it looks like the drawing code really interprets it as unbounded letting it go as high as the number of rows to draw. For CPU-bound work it doesn't make sense to go above the number of CPU-s available.
Long story short, `-1' should mean "let the runtime decide", and we should not attempt to outsmart it.