Skip to content

Commit 24af798

Browse files
Fix parallel bounds, use configuration
1 parent a4d0378 commit 24af798

File tree

5 files changed

+98
-20
lines changed

5 files changed

+98
-20
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ 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 leave the degree of parallelism unbounded.
123+
/// The maximum degree of parallelism to use when building the scene, or <c>-1</c> to pass
124+
/// through the runtime's unlimited sentinel for <see cref="ParallelOptions.MaxDegreeOfParallelism"/>.
124125
/// </param>
125126
/// <returns>A flush-ready scene.</returns>
126127
public static FlushScene Create(

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

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,39 +13,52 @@ internal static class ParallelExecutionHelper
1313
/// Computes the number of partitions to schedule for work constrained by a single work-item limit.
1414
/// </summary>
1515
/// <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.
16+
/// The configured maximum degree of parallelism. A value of <c>-1</c> leaves the runtime
17+
/// parallelism cap unbounded, but partition planning remains capped to
18+
/// <see cref="Environment.ProcessorCount"/> to avoid excessive fan-out.
1819
/// </param>
1920
/// <param name="workItemCount">The total number of work items available for partitioning.</param>
2021
/// <returns>The number of partitions to schedule.</returns>
2122
public static int GetPartitionCount(int maxDegreeOfParallelism, int workItemCount)
22-
=> maxDegreeOfParallelism == -1 ? workItemCount : Math.Min(maxDegreeOfParallelism, workItemCount);
23+
=> Math.Min(GetPartitionLimit(maxDegreeOfParallelism), workItemCount);
2324

2425
/// <summary>
2526
/// Computes the number of partitions to schedule for work constrained by two independent limits.
2627
/// </summary>
2728
/// <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.
29+
/// The configured maximum degree of parallelism. A value of <c>-1</c> leaves the runtime
30+
/// parallelism cap unbounded, but partition planning remains capped to
31+
/// <see cref="Environment.ProcessorCount"/> to avoid excessive fan-out.
3032
/// </param>
3133
/// <param name="workItemCount">The total number of work items available for partitioning.</param>
3234
/// <param name="secondaryLimit">An additional caller-specific upper bound on useful partitions.</param>
3335
/// <returns>The number of partitions to schedule.</returns>
3436
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));
37+
=> Math.Min(GetPartitionLimit(maxDegreeOfParallelism), Math.Min(workItemCount, secondaryLimit));
3838

3939
/// <summary>
4040
/// Creates the <see cref="ParallelOptions"/> for a partitioned operation while preserving the
41-
/// special meaning of <c>-1</c> as unbounded parallelism.
41+
/// special meaning of <c>-1</c> in <see cref="ParallelOptions.MaxDegreeOfParallelism"/>.
4242
/// </summary>
4343
/// <param name="maxDegreeOfParallelism">
4444
/// The configured maximum degree of parallelism. A value of <c>-1</c> is propagated directly
45-
/// to <see cref="ParallelOptions.MaxDegreeOfParallelism"/>.
45+
/// to <see cref="ParallelOptions.MaxDegreeOfParallelism"/>; positive values are capped to the
46+
/// smaller of the configured limit and the useful partition count.
4647
/// </param>
4748
/// <param name="partitionCount">The computed number of useful partitions for the operation.</param>
4849
/// <returns>The <see cref="ParallelOptions"/> instance for the operation.</returns>
4950
public static ParallelOptions CreateParallelOptions(int maxDegreeOfParallelism, int partitionCount)
50-
=> new() { MaxDegreeOfParallelism = maxDegreeOfParallelism == -1 ? -1 : partitionCount };
51+
=> new() { MaxDegreeOfParallelism = maxDegreeOfParallelism == -1 ? -1 : Math.Min(maxDegreeOfParallelism, partitionCount) };
52+
53+
/// <summary>
54+
/// Computes the internal partition-planning cap for the configured parallelism setting.
55+
/// </summary>
56+
/// <param name="maxDegreeOfParallelism">
57+
/// The configured maximum degree of parallelism. A value of <c>-1</c> keeps the runtime
58+
/// parallelism setting unbounded, but partition planning is capped to
59+
/// <see cref="Environment.ProcessorCount"/>.
60+
/// </param>
61+
/// <returns>The maximum number of partitions to plan for.</returns>
62+
private static int GetPartitionLimit(int maxDegreeOfParallelism)
63+
=> maxDegreeOfParallelism == -1 ? Environment.ProcessorCount : maxDegreeOfParallelism;
5164
}

src/ImageSharp.Drawing/Processing/DrawingCanvasExtensions.cs

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,30 @@ public static DrawingCanvas<TPixel> CreateCanvas<TPixel>(
2323
DrawingOptions options,
2424
params IPath[] clipPaths)
2525
where TPixel : unmanaged, IPixel<TPixel>
26+
=> CreateCanvas(frame, frame.Configuration, options, clipPaths);
27+
28+
/// <summary>
29+
/// Creates a drawing canvas over an existing frame.
30+
/// </summary>
31+
/// <typeparam name="TPixel">The pixel format.</typeparam>
32+
/// <param name="frame">The frame backing the canvas.</param>
33+
/// <param name="configuration">The configuration to use for this canvas instance.</param>
34+
/// <param name="options">Initial drawing options for this canvas instance.</param>
35+
/// <param name="clipPaths">Initial clip paths for this canvas instance.</param>
36+
/// <returns>A drawing canvas targeting <paramref name="frame"/>.</returns>
37+
public static DrawingCanvas<TPixel> CreateCanvas<TPixel>(
38+
this ImageFrame<TPixel> frame,
39+
Configuration configuration,
40+
DrawingOptions options,
41+
params IPath[] clipPaths)
42+
where TPixel : unmanaged, IPixel<TPixel>
2643
{
2744
Guard.NotNull(frame, nameof(frame));
2845
Guard.NotNull(options, nameof(options));
2946
Guard.NotNull(clipPaths, nameof(clipPaths));
3047

3148
return new DrawingCanvas<TPixel>(
32-
frame.Configuration,
49+
configuration,
3350
new Buffer2DRegion<TPixel>(frame.PixelBuffer, frame.Bounds),
3451
options,
3552
clipPaths);
@@ -50,13 +67,32 @@ public static DrawingCanvas<TPixel> CreateCanvas<TPixel>(
5067
DrawingOptions options,
5168
params IPath[] clipPaths)
5269
where TPixel : unmanaged, IPixel<TPixel>
70+
=> CreateCanvas(image, frameIndex, image.Configuration, options, clipPaths);
71+
72+
/// <summary>
73+
/// Creates a drawing canvas over a specific frame of an image.
74+
/// </summary>
75+
/// <typeparam name="TPixel">The pixel format.</typeparam>
76+
/// <param name="image">The image containing the frame.</param>
77+
/// <param name="frameIndex">The zero-based frame index to target.</param>
78+
/// <param name="configuration">The configuration to use for this canvas instance.</param>
79+
/// <param name="options">Initial drawing options for this canvas instance.</param>
80+
/// <param name="clipPaths">Initial clip paths for this canvas instance.</param>
81+
/// <returns>A drawing canvas targeting the selected frame.</returns>
82+
public static DrawingCanvas<TPixel> CreateCanvas<TPixel>(
83+
this Image<TPixel> image,
84+
int frameIndex,
85+
Configuration configuration,
86+
DrawingOptions options,
87+
params IPath[] clipPaths)
88+
where TPixel : unmanaged, IPixel<TPixel>
5389
{
5490
Guard.NotNull(image, nameof(image));
5591
Guard.NotNull(options, nameof(options));
5692
Guard.NotNull(clipPaths, nameof(clipPaths));
5793
Guard.MustBeBetweenOrEqualTo(frameIndex, 0, image.Frames.Count - 1, nameof(frameIndex));
5894

59-
return image.Frames[frameIndex].CreateCanvas(options, clipPaths);
95+
return image.Frames[frameIndex].CreateCanvas(configuration, options, clipPaths);
6096
}
6197

6298
/// <summary>
@@ -72,11 +108,28 @@ public static DrawingCanvas<TPixel> CreateCanvas<TPixel>(
72108
DrawingOptions options,
73109
params IPath[] clipPaths)
74110
where TPixel : unmanaged, IPixel<TPixel>
111+
=> CreateCanvas(image, image.Configuration, options, clipPaths);
112+
113+
/// <summary>
114+
/// Creates a drawing canvas over the root frame of an image.
115+
/// </summary>
116+
/// <typeparam name="TPixel">The pixel format.</typeparam>
117+
/// <param name="image">The image whose root frame should be targeted.</param>
118+
/// <param name="configuration">The configuration to use for this canvas instance.</param>
119+
/// <param name="options">Initial drawing options for this canvas instance.</param>
120+
/// <param name="clipPaths">Initial clip paths for this canvas instance.</param>
121+
/// <returns>A drawing canvas targeting the root frame.</returns>
122+
public static DrawingCanvas<TPixel> CreateCanvas<TPixel>(
123+
this Image<TPixel> image,
124+
Configuration configuration,
125+
DrawingOptions options,
126+
params IPath[] clipPaths)
127+
where TPixel : unmanaged, IPixel<TPixel>
75128
{
76129
Guard.NotNull(image, nameof(image));
77130
Guard.NotNull(options, nameof(options));
78131
Guard.NotNull(clipPaths, nameof(clipPaths));
79132

80-
return image.Frames.RootFrame.CreateCanvas(options, clipPaths);
133+
return image.Frames.RootFrame.CreateCanvas(configuration, options, clipPaths);
81134
}
82135
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public ProcessWithCanvasProcessor(
3636
/// <inheritdoc />
3737
protected override void OnFrameApply(ImageFrame<TPixel> source)
3838
{
39-
using DrawingCanvas<TPixel> canvas = source.CreateCanvas(this.definition.Options);
39+
using DrawingCanvas<TPixel> canvas = source.CreateCanvas(this.Configuration, this.definition.Options);
4040
this.action(canvas);
4141
}
4242
}

tests/ImageSharp.Drawing.Tests/Processing/Backends/ParallelExecutionHelperTests.cs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,34 @@ namespace SixLabors.ImageSharp.Drawing.Tests.Processing.Backends;
88
public class ParallelExecutionHelperTests
99
{
1010
[Theory]
11-
[InlineData(-1, 8, 8)]
1211
[InlineData(4, 8, 4)]
1312
[InlineData(1, 8, 1)]
1413
public void GetPartitionCountSingleLimit(int maxDegreeOfParallelism, int workItemCount, int expected)
1514
=> Assert.Equal(expected, ParallelExecutionHelper.GetPartitionCount(maxDegreeOfParallelism, workItemCount));
1615

16+
[Fact]
17+
public void GetPartitionCountSingleLimit_UnboundedSettingIsCappedToProcessorCount()
18+
=> Assert.Equal(
19+
Math.Min(Environment.ProcessorCount, 8),
20+
ParallelExecutionHelper.GetPartitionCount(-1, 8));
21+
1722
[Theory]
18-
[InlineData(-1, 8, 3, 3)]
1923
[InlineData(8, 8, 3, 3)]
2024
[InlineData(2, 8, 3, 2)]
2125
public void GetPartitionCountDualLimit(int maxDegreeOfParallelism, int workItemCount, int secondaryLimit, int expected)
2226
=> Assert.Equal(expected, ParallelExecutionHelper.GetPartitionCount(maxDegreeOfParallelism, workItemCount, secondaryLimit));
2327

28+
[Fact]
29+
public void GetPartitionCountDualLimit_UnboundedSettingIsCappedToProcessorCount()
30+
=> Assert.Equal(
31+
Math.Min(Environment.ProcessorCount, 3),
32+
ParallelExecutionHelper.GetPartitionCount(-1, 8, 3));
33+
2434
[Theory]
2535
[InlineData(-1, 8, -1)]
26-
[InlineData(4, 8, 8)]
36+
[InlineData(4, 8, 4)]
37+
[InlineData(4, 2, 2)]
2738
[InlineData(2, 2, 2)]
28-
public void CreateParallelOptionsPreservesUnboundedSetting(int maxDegreeOfParallelism, int partitionCount, int expected)
39+
public void CreateParallelOptions_MatchesParallelOptionsContract(int maxDegreeOfParallelism, int partitionCount, int expected)
2940
=> Assert.Equal(expected, ParallelExecutionHelper.CreateParallelOptions(maxDegreeOfParallelism, partitionCount).MaxDegreeOfParallelism);
3041
}

0 commit comments

Comments
 (0)