diff --git a/src/ImageSharp/Common/Helpers/ParallelFor.cs b/src/ImageSharp/Common/Helpers/ParallelFor.cs
index 7aa50a9e3..579ae1257 100644
--- a/src/ImageSharp/Common/Helpers/ParallelFor.cs
+++ b/src/ImageSharp/Common/Helpers/ParallelFor.cs
@@ -1,63 +1,12 @@
using System;
using System.Buffers;
-using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory;
-using SixLabors.Primitives;
namespace SixLabors.ImageSharp
{
- internal readonly struct RowInterval
- {
- public RowInterval(int min, int max)
- {
- this.Min = min;
- this.Max = max;
- }
-
- ///
- /// Gets the INCLUSIVE minimum
- ///
- public int Min { get; }
-
- ///
- /// Gets the EXCLUSIVE maximum
- ///
- public int Max { get; }
- }
-
- internal static class ParallelHelper
- {
- public static void IterateRows(in Rectangle rectangle, Configuration configuration, Action body)
- {
- int maxSteps = (int)Math.Ceiling(
- (float)(rectangle.Width * rectangle.Height) / configuration.MinimumPixelsProcessedPerTask);
-
- int numOfSteps = Math.Min(configuration.MaxDegreeOfParallelism, maxSteps);
-
- int step = rectangle.Height / numOfSteps;
-
- var parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = numOfSteps };
-
- int bottom = rectangle.Bottom;
-
- Parallel.For(
- 0,
- numOfSteps,
- parallelOptions,
- i =>
- {
- int yMin = i * step;
- int yMax = Math.Min(yMin + step, bottom);
-
- var rowInterval = new RowInterval(yMin, yMax);
- body(rowInterval);
- });
- }
- }
-
///
/// Utility methods for Parallel.For() execution. Use this instead of raw calls!
///
diff --git a/src/ImageSharp/Common/ParallelUtils/ParallelExecutionSettings.cs b/src/ImageSharp/Common/ParallelUtils/ParallelExecutionSettings.cs
new file mode 100644
index 000000000..862ea1361
--- /dev/null
+++ b/src/ImageSharp/Common/ParallelUtils/ParallelExecutionSettings.cs
@@ -0,0 +1,36 @@
+// Copyright(c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Threading.Tasks;
+
+namespace SixLabors.ImageSharp.ParallelUtils
+{
+ ///
+ /// Defines execution settings for methods in .
+ ///
+ internal struct ParallelExecutionSettings
+ {
+ public ParallelExecutionSettings(int maxDegreeOfParallelism, int minimumPixelsProcessedPerTask)
+ {
+ this.MaxDegreeOfParallelism = maxDegreeOfParallelism;
+ this.MinimumPixelsProcessedPerTask = minimumPixelsProcessedPerTask;
+ }
+
+ public ParallelExecutionSettings(int maxDegreeOfParallelism)
+ : this(maxDegreeOfParallelism, 2048)
+ {
+ }
+
+ ///
+ /// Gets the value used for initializing when using TPL.
+ ///
+ public int MaxDegreeOfParallelism { get; }
+
+ ///
+ /// Gets the minimum number of pixels being processed by a single task when parallelizing operations with TPL.
+ /// Launching tasks for pixel regions below this limit is not worth the overhead.
+ /// Initialized with 2048 by default, the optimum value is operation specific.
+ ///
+ public int MinimumPixelsProcessedPerTask { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs b/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs
new file mode 100644
index 000000000..d7cc3b8b1
--- /dev/null
+++ b/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs
@@ -0,0 +1,95 @@
+// Copyright(c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+
+using SixLabors.ImageSharp.Memory;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.ParallelUtils
+{
+ ///
+ /// Utility methods wrapping Parallel.For() execution optimized for image processing.
+ /// Use this instead of direct calls!
+ ///
+ internal static class ParallelHelper
+ {
+ ///
+ /// Get the default for a
+ ///
+ public static ParallelExecutionSettings GetParallelSettings(this Configuration configuration)
+ {
+ return new ParallelExecutionSettings(configuration.MaxDegreeOfParallelism);
+ }
+
+ ///
+ /// Gets a span for all the pixels in defined by
+ ///
+ public static Span GetMultiRowSpan(this Buffer2D buffer, in RowInterval rows)
+ where T : struct
+ {
+ return buffer.Span.Slice(rows.Min * buffer.Width, rows.Height * buffer.Width);
+ }
+
+ ///
+ /// Iterate through the rows of a rectangle in optimized batches defined by -s.
+ ///
+ public static void IterateRows(Rectangle rectangle, Configuration configuration, Action body)
+ {
+ ParallelExecutionSettings parallelSettings = configuration.GetParallelSettings();
+
+ IterateRows(rectangle, parallelSettings, body);
+ }
+
+ ///
+ /// Iterate through the rows of a rectangle in optimized batches defined by -s.
+ ///
+ public static void IterateRows(Rectangle rectangle, in ParallelExecutionSettings parallelSettings, Action body)
+ {
+ int maxSteps = DivideCeil(rectangle.Width * rectangle.Height, parallelSettings.MinimumPixelsProcessedPerTask);
+
+ int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
+
+ // Avoid TPL overhead in this trivial case:
+ if (numOfSteps == 1)
+ {
+ var rows = new RowInterval(rectangle.Top, rectangle.Bottom);
+ body(rows);
+ return;
+ }
+
+ int verticalStep = DivideRound(rectangle.Height, numOfSteps);
+
+ var parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = numOfSteps };
+
+ Parallel.For(
+ 0,
+ numOfSteps,
+ parallelOptions,
+ i =>
+ {
+ int yMin = rectangle.Top + (i * verticalStep);
+ int yMax = Math.Min(yMin + verticalStep, rectangle.Bottom);
+
+ var rowInterval = new RowInterval(yMin, yMax);
+ body(rowInterval);
+ });
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int DivideCeil(int dividend, int divisor)
+ {
+ // TODO: Is there a more efficient way to calculate this?
+ int result = dividend / divisor;
+ return dividend % divisor == 0 ? result : result + 1;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int DivideRound(int dividend, int divisor)
+ {
+ return (dividend + (divisor / 2)) / divisor;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Common/ParallelUtils/RowInterval.cs b/src/ImageSharp/Common/ParallelUtils/RowInterval.cs
new file mode 100644
index 000000000..5a30dd741
--- /dev/null
+++ b/src/ImageSharp/Common/ParallelUtils/RowInterval.cs
@@ -0,0 +1,35 @@
+// Copyright(c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using SixLabors.ImageSharp.Memory;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.ParallelUtils
+{
+ ///
+ /// Represents an interval of rows in a and/or
+ ///
+ internal readonly struct RowInterval
+ {
+ public RowInterval(int min, int max)
+ {
+ this.Min = min;
+ this.Max = max;
+ }
+
+ ///
+ /// Gets the INCLUSIVE minimum
+ ///
+ public int Min { get; }
+
+ ///
+ /// Gets the EXCLUSIVE maximum
+ ///
+ public int Max { get; }
+
+ ///
+ /// Gets the difference ( - )
+ ///
+ public int Height => this.Max - this.Min;
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs
index 4bd6bd9e5..c54d02fa3 100644
--- a/src/ImageSharp/Configuration.cs
+++ b/src/ImageSharp/Configuration.cs
@@ -29,8 +29,6 @@ namespace SixLabors.ImageSharp
private int maxDegreeOfParallelism = Environment.ProcessorCount;
- private int minimumPixelsPerTask = 2048;
-
///
/// Initializes a new instance of the class.
///
@@ -77,24 +75,6 @@ namespace SixLabors.ImageSharp
}
}
- ///
- /// Gets or sets the minimum number of pixels being processed by a single task when parallelizing operations with TPL.
- /// It's not worth to launch tasks processing pixels below this limit. Initialized with 2048 by default.
- ///
- public int MinimumPixelsProcessedPerTask
- {
- get => this.minimumPixelsPerTask;
- set
- {
- if (value <= 0)
- {
- throw new ArgumentOutOfRangeException(nameof(this.MinimumPixelsProcessedPerTask));
- }
-
- this.minimumPixelsPerTask = value;
- }
- }
-
///
/// Gets the currently registered s.
///
diff --git a/src/ImageSharp/ImageSharp.csproj.DotSettings b/src/ImageSharp/ImageSharp.csproj.DotSettings
new file mode 100644
index 000000000..8b2e1bcf0
--- /dev/null
+++ b/src/ImageSharp/ImageSharp.csproj.DotSettings
@@ -0,0 +1,3 @@
+
+ True
+ True
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs
index ca904f953..6c0b57057 100644
--- a/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs
+++ b/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs
@@ -3,9 +3,8 @@
using System.Threading;
-using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.Primitives;
-
using Xunit;
namespace SixLabors.ImageSharp.Tests.Helpers
@@ -15,29 +14,31 @@ namespace SixLabors.ImageSharp.Tests.Helpers
[Theory]
[InlineData(1, 0, 100, -1, 100)]
[InlineData(2, 0, 9, 5, 4)]
+ [InlineData(4, 0, 19, 5, 4)]
[InlineData(2, 10, 19, 5, 4)]
[InlineData(4, 0, 200, 50, 50)]
[InlineData(4, 123, 323, 50, 50)]
+ [InlineData(4, 0, 1201, 300, 301)]
public void IterateRows_OverMinimumPixelsLimit(
int maxDegreeOfParallelism,
int minY,
int maxY,
- int expectedStep,
- int expectedLastStep)
+ int expectedStepLength,
+ int expectedLastStepLength)
{
- Configuration cfg = Configuration.Default.ShallowCopy();
- cfg.MinimumPixelsProcessedPerTask = 1;
- cfg.MaxDegreeOfParallelism = maxDegreeOfParallelism;
+ var parallelSettings = new ParallelExecutionSettings(maxDegreeOfParallelism, 1);
- var rectangle = new Rectangle(0, minY, 10, maxY);
+ var rectangle = new Rectangle(0, minY, 10, maxY - minY);
int actualNumberOfSteps = 0;
- ParallelHelper.IterateRows(rectangle, cfg,
+ ParallelHelper.IterateRows(rectangle, parallelSettings,
rows =>
{
Assert.True(rows.Min >= minY);
+ Assert.True(rows.Max <= maxY);
+
int step = rows.Max - rows.Min;
- int expected = rows.Max < maxY ? expectedStep : expectedLastStep;
+ int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength;
Interlocked.Increment(ref actualNumberOfSteps);
Assert.Equal(expected, step);
@@ -45,33 +46,38 @@ namespace SixLabors.ImageSharp.Tests.Helpers
Assert.Equal(maxDegreeOfParallelism, actualNumberOfSteps);
}
+
[Theory]
[InlineData(2, 200, 50, 2, 1, -1, 2)]
[InlineData(2, 200, 200, 1, 1, -1, 1)]
- [InlineData(4, 200, 100, 4, 2, 1, 1)]
+ [InlineData(4, 200, 100, 4, 2, 2, 2)]
[InlineData(4, 300, 100, 8, 3, 3, 2)]
+ [InlineData(2, 5000, 1, 4500, 1, -1, 4500)]
+ [InlineData(2, 5000, 1, 5000, 1, -1, 5000)]
+ [InlineData(2, 5000, 1, 5001, 2, 2501, 2500)]
public void IterateRows_WithEffectiveMinimumPixelsLimit(
int maxDegreeOfParallelism,
int minimumPixelsProcessedPerTask,
int width,
int height,
int expectedNumberOfSteps,
- int expectedStep,
- int expectedLastStep)
+ int expectedStepLength,
+ int expectedLastStepLength)
{
- Configuration cfg = Configuration.Default.ShallowCopy();
- cfg.MinimumPixelsProcessedPerTask = minimumPixelsProcessedPerTask;
- cfg.MaxDegreeOfParallelism = maxDegreeOfParallelism;
+ var parallelSettings = new ParallelExecutionSettings(maxDegreeOfParallelism, minimumPixelsProcessedPerTask);
var rectangle = new Rectangle(0, 0, width, height);
int actualNumberOfSteps = 0;
- ParallelHelper.IterateRows(rectangle, cfg,
+ ParallelHelper.IterateRows(rectangle, parallelSettings,
rows =>
{
+ Assert.True(rows.Min >= 0);
+ Assert.True(rows.Max <= height);
+
int step = rows.Max - rows.Min;
- int expected = rows.Max < height ? expectedStep : expectedLastStep;
+ int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength;
Interlocked.Increment(ref actualNumberOfSteps);
Assert.Equal(expected, step);