Skip to content

Commit 7fc45bb

Browse files
Add ParallelExecutionHelper for partitioning
1 parent d07e429 commit 7fc45bb

File tree

5 files changed

+88
-12
lines changed

5 files changed

+88
-12
lines changed

src/ImageSharp.Drawing/Processing/Backends/DefaultDrawingBackend.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,12 @@ private static void ExecuteScene<TPixel>(
8787
}
8888

8989
int requestedParallelism = configuration.MaxDegreeOfParallelism;
90-
int partitionCount = Math.Min(
91-
scene.RowCount,
92-
requestedParallelism == -1 ? Environment.ProcessorCount : requestedParallelism);
90+
int partitionCount = ParallelExecutionHelper.GetPartitionCount(requestedParallelism, scene.RowCount);
9391

9492
_ = Parallel.For(
9593
fromInclusive: 0,
9694
toExclusive: scene.RowCount,
97-
parallelOptions: new ParallelOptions { MaxDegreeOfParallelism = partitionCount },
95+
parallelOptions: ParallelExecutionHelper.CreateParallelOptions(requestedParallelism, partitionCount),
9896
localInit: () => new WorkerState<TPixel>(configuration.MemoryAllocator, destinationFrame.Width, scene.MaxLayerDepth + 1),
9997
body: (rowIndex, _, state) =>
10098
{

src/ImageSharp.Drawing/Processing/Backends/FlushScene.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ private FlushScene(
120120
/// <param name="targetBounds">The destination bounds of the flush.</param>
121121
/// <param name="allocator">The allocator used for retained row storage.</param>
122122
/// <param name="maxDegreeOfParallelism">
123-
/// The maximum degree of parallelism to use when building the scene, or -1 to use the default number of processors.
123+
/// The maximum degree of parallelism to use when building the scene, or -1 to leave the degree of parallelism unbounded.
124124
/// </param>
125125
/// <returns>A flush-ready scene.</returns>
126126
public static FlushScene Create(
@@ -149,14 +149,13 @@ public static FlushScene Create(
149149

150150
FillSceneItem?[] fillItems = new FillSceneItem?[commandCount];
151151
StrokeSceneItem?[] strokeItems = new StrokeSceneItem?[commandCount];
152-
int availableParallelism = maxDegreeOfParallelism == -1 ? Environment.ProcessorCount : maxDegreeOfParallelism;
153-
int partitionCount = Math.Min(commandCount, Math.Min(availableParallelism, targetRowCount));
152+
int partitionCount = ParallelExecutionHelper.GetPartitionCount(maxDegreeOfParallelism, commandCount, targetRowCount);
154153
PartitionState[] partitions = new PartitionState[partitionCount];
155154

156155
_ = Parallel.For(
157156
0,
158157
partitionCount,
159-
new ParallelOptions { MaxDegreeOfParallelism = partitionCount },
158+
ParallelExecutionHelper.CreateParallelOptions(maxDegreeOfParallelism, partitionCount),
160159
partitionIndex =>
161160
{
162161
// Integer division splits the commands into contiguous half-open ranges,
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
namespace SixLabors.ImageSharp.Drawing.Processing.Backends;
5+
6+
/// <summary>
7+
/// Centralizes the conversion from configuration parallelism settings to partition counts and
8+
/// <see cref="ParallelOptions"/> instances used by retained-scene CPU execution paths.
9+
/// </summary>
10+
internal static class ParallelExecutionHelper
11+
{
12+
/// <summary>
13+
/// Computes the number of partitions to schedule for work constrained by a single work-item limit.
14+
/// </summary>
15+
/// <param name="maxDegreeOfParallelism">
16+
/// The configured maximum degree of parallelism. A value of <c>-1</c> leaves the degree of
17+
/// parallelism unbounded, so the work-item count is used as the effective partition cap.
18+
/// </param>
19+
/// <param name="workItemCount">The total number of work items available for partitioning.</param>
20+
/// <returns>The number of partitions to schedule.</returns>
21+
public static int GetPartitionCount(int maxDegreeOfParallelism, int workItemCount)
22+
=> maxDegreeOfParallelism == -1 ? workItemCount : Math.Min(maxDegreeOfParallelism, workItemCount);
23+
24+
/// <summary>
25+
/// Computes the number of partitions to schedule for work constrained by two independent limits.
26+
/// </summary>
27+
/// <param name="maxDegreeOfParallelism">
28+
/// The configured maximum degree of parallelism. A value of <c>-1</c> leaves the degree of
29+
/// parallelism unbounded, so only the supplied work limits constrain the partition count.
30+
/// </param>
31+
/// <param name="workItemCount">The total number of work items available for partitioning.</param>
32+
/// <param name="secondaryLimit">An additional caller-specific upper bound on useful partitions.</param>
33+
/// <returns>The number of partitions to schedule.</returns>
34+
public static int GetPartitionCount(int maxDegreeOfParallelism, int workItemCount, int secondaryLimit)
35+
=> maxDegreeOfParallelism == -1
36+
? Math.Min(workItemCount, secondaryLimit)
37+
: Math.Min(maxDegreeOfParallelism, Math.Min(workItemCount, secondaryLimit));
38+
39+
/// <summary>
40+
/// Creates the <see cref="ParallelOptions"/> for a partitioned operation while preserving the
41+
/// special meaning of <c>-1</c> as unbounded parallelism.
42+
/// </summary>
43+
/// <param name="maxDegreeOfParallelism">
44+
/// The configured maximum degree of parallelism. A value of <c>-1</c> is propagated directly
45+
/// to <see cref="ParallelOptions.MaxDegreeOfParallelism"/>.
46+
/// </param>
47+
/// <param name="partitionCount">The computed number of useful partitions for the operation.</param>
48+
/// <returns>The <see cref="ParallelOptions"/> instance for the operation.</returns>
49+
public static ParallelOptions CreateParallelOptions(int maxDegreeOfParallelism, int partitionCount)
50+
=> new() { MaxDegreeOfParallelism = maxDegreeOfParallelism == -1 ? -1 : partitionCount };
51+
}

src/ImageSharp.Drawing/Processing/DrawingCanvasBatcher{TPixel}.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,7 @@ private void PrepareCommands()
151151
// to the backend. This avoids complicating the backend with clipping logic
152152
// and allows us to reuse the same optimized backend code for clipped and unclipped paths.
153153
int requestedParallelism = this.configuration.MaxDegreeOfParallelism;
154-
int partitionCount = Math.Min(
155-
this.commandCount,
156-
requestedParallelism == -1 ? Environment.ProcessorCount : requestedParallelism);
154+
int partitionCount = ParallelExecutionHelper.GetPartitionCount(requestedParallelism, this.commandCount);
157155

158156
if (partitionCount <= 1)
159157
{
@@ -168,7 +166,7 @@ private void PrepareCommands()
168166
_ = Parallel.For(
169167
0,
170168
partitionCount,
171-
new ParallelOptions() { MaxDegreeOfParallelism = partitionCount },
169+
ParallelExecutionHelper.CreateParallelOptions(requestedParallelism, partitionCount),
172170
partitionIndex =>
173171
{
174172
// Integer division splits the commands into contiguous half-open ranges,
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using SixLabors.ImageSharp.Drawing.Processing.Backends;
5+
6+
namespace SixLabors.ImageSharp.Drawing.Tests.Processing.Backends;
7+
8+
public class ParallelExecutionHelperTests
9+
{
10+
[Theory]
11+
[InlineData(-1, 8, 8)]
12+
[InlineData(4, 8, 4)]
13+
[InlineData(1, 8, 1)]
14+
public void GetPartitionCountSingleLimit(int maxDegreeOfParallelism, int workItemCount, int expected)
15+
=> Assert.Equal(expected, ParallelExecutionHelper.GetPartitionCount(maxDegreeOfParallelism, workItemCount));
16+
17+
[Theory]
18+
[InlineData(-1, 8, 3, 3)]
19+
[InlineData(8, 8, 3, 3)]
20+
[InlineData(2, 8, 3, 2)]
21+
public void GetPartitionCountDualLimit(int maxDegreeOfParallelism, int workItemCount, int secondaryLimit, int expected)
22+
=> Assert.Equal(expected, ParallelExecutionHelper.GetPartitionCount(maxDegreeOfParallelism, workItemCount, secondaryLimit));
23+
24+
[Theory]
25+
[InlineData(-1, 8, -1)]
26+
[InlineData(4, 8, 8)]
27+
[InlineData(2, 2, 2)]
28+
public void CreateParallelOptionsPreservesUnboundedSetting(int maxDegreeOfParallelism, int partitionCount, int expected)
29+
=> Assert.Equal(expected, ParallelExecutionHelper.CreateParallelOptions(maxDegreeOfParallelism, partitionCount).MaxDegreeOfParallelism);
30+
}

0 commit comments

Comments
 (0)