diff --git a/src/ImageSharp/Common/ParallelUtils/ParallelExecutionSettings.cs b/src/ImageSharp/Common/ParallelUtils/ParallelExecutionSettings.cs
index 862ea1361..89d5fc18d 100644
--- a/src/ImageSharp/Common/ParallelUtils/ParallelExecutionSettings.cs
+++ b/src/ImageSharp/Common/ParallelUtils/ParallelExecutionSettings.cs
@@ -3,6 +3,8 @@
using System.Threading.Tasks;
+using SixLabors.Memory;
+
namespace SixLabors.ImageSharp.ParallelUtils
{
///
@@ -10,17 +12,20 @@ namespace SixLabors.ImageSharp.ParallelUtils
///
internal struct ParallelExecutionSettings
{
- public ParallelExecutionSettings(int maxDegreeOfParallelism, int minimumPixelsProcessedPerTask)
+ public ParallelExecutionSettings(int maxDegreeOfParallelism, int minimumPixelsProcessedPerTask, MemoryAllocator memoryAllocator)
{
this.MaxDegreeOfParallelism = maxDegreeOfParallelism;
this.MinimumPixelsProcessedPerTask = minimumPixelsProcessedPerTask;
+ this.MemoryAllocator = memoryAllocator;
}
- public ParallelExecutionSettings(int maxDegreeOfParallelism)
- : this(maxDegreeOfParallelism, 2048)
+ public ParallelExecutionSettings(int maxDegreeOfParallelism, MemoryAllocator memoryAllocator)
+ : this(maxDegreeOfParallelism, 2048, memoryAllocator)
{
}
+ public MemoryAllocator MemoryAllocator { get; }
+
///
/// Gets the value used for initializing when using TPL.
///
diff --git a/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs b/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs
index d7cc3b8b1..a757f50c8 100644
--- a/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs
+++ b/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs
@@ -2,10 +2,12 @@
// Licensed under the Apache License, Version 2.0.
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.ParallelUtils
@@ -21,16 +23,7 @@ namespace SixLabors.ImageSharp.ParallelUtils
///
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);
+ return new ParallelExecutionSettings(configuration.MaxDegreeOfParallelism, configuration.MemoryAllocator);
}
///
@@ -46,7 +39,10 @@ namespace SixLabors.ImageSharp.ParallelUtils
///
/// Iterate through the rows of a rectangle in optimized batches defined by -s.
///
- public static void IterateRows(Rectangle rectangle, in ParallelExecutionSettings parallelSettings, Action body)
+ public static void IterateRows(
+ Rectangle rectangle,
+ in ParallelExecutionSettings parallelSettings,
+ Action body)
{
int maxSteps = DivideCeil(rectangle.Width * rectangle.Height, parallelSettings.MinimumPixelsProcessedPerTask);
@@ -73,8 +69,58 @@ namespace SixLabors.ImageSharp.ParallelUtils
int yMin = rectangle.Top + (i * verticalStep);
int yMax = Math.Min(yMin + verticalStep, rectangle.Bottom);
- var rowInterval = new RowInterval(yMin, yMax);
- body(rowInterval);
+ var rows = new RowInterval(yMin, yMax);
+ body(rows);
+ });
+ }
+
+ ///
+ /// Iterate through the rows of a rectangle in optimized batches defined by -s
+ /// instantiating a temporary buffer for each invocation.
+ ///
+ public static void IterateRowsWithTempBuffer(
+ Rectangle rectangle,
+ in ParallelExecutionSettings parallelSettings,
+ Action> body)
+ where T : struct
+ {
+ int maxSteps = DivideCeil(rectangle.Width * rectangle.Height, parallelSettings.MinimumPixelsProcessedPerTask);
+
+ int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
+
+ MemoryAllocator memoryAllocator = parallelSettings.MemoryAllocator;
+
+ // Avoid TPL overhead in this trivial case:
+ if (numOfSteps == 1)
+ {
+ var rows = new RowInterval(rectangle.Top, rectangle.Bottom);
+ using (IMemoryOwner buffer = memoryAllocator.Allocate(rectangle.Width))
+ {
+ body(rows, buffer.Memory);
+ }
+
+ 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 rows = new RowInterval(yMin, yMax);
+
+ using (IMemoryOwner buffer = memoryAllocator.Allocate(rectangle.Width))
+ {
+ body(rows, buffer.Memory);
+ }
});
}
diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs
index a9edb9cfb..17ab6e252 100644
--- a/src/ImageSharp/Memory/Buffer2DExtensions.cs
+++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs
@@ -113,5 +113,14 @@ namespace SixLabors.ImageSharp.Memory
/// The
public static BufferArea GetArea(this Buffer2D buffer)
where T : struct => new BufferArea(buffer);
+
+ ///
+ /// 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);
+ }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Common/ParallelUtils/RowInterval.cs b/src/ImageSharp/Memory/RowInterval.cs
similarity index 89%
rename from src/ImageSharp/Common/ParallelUtils/RowInterval.cs
rename to src/ImageSharp/Memory/RowInterval.cs
index 5a30dd741..87b08251b 100644
--- a/src/ImageSharp/Common/ParallelUtils/RowInterval.cs
+++ b/src/ImageSharp/Memory/RowInterval.cs
@@ -1,10 +1,9 @@
// 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
+namespace SixLabors.ImageSharp.Memory
{
///
/// Represents an interval of rows in a and/or
@@ -13,6 +12,8 @@ namespace SixLabors.ImageSharp.ParallelUtils
{
public RowInterval(int min, int max)
{
+ DebugGuard.MustBeLessThan(min, max, nameof(min));
+
this.Min = min;
this.Max = max;
}
diff --git a/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs
index 6c0b57057..35bf1489b 100644
--- a/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs
+++ b/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs
@@ -1,24 +1,39 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System;
+using System.Collections.Concurrent;
+using System.Linq;
+using System.Numerics;
using System.Threading;
+using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.Primitives;
+
using Xunit;
namespace SixLabors.ImageSharp.Tests.Helpers
{
public class ParallelHelperTests
{
+ ///
+ /// maxDegreeOfParallelism, minY, maxY, expectedStepLength, expectedLastStepLength
+ ///
+ public static TheoryData IterateRows_OverMinimumPixelsLimit_Data =
+ new TheoryData()
+ {
+ { 1, 0, 100, -1, 100 },
+ { 2, 0, 9, 5, 4 },
+ { 4, 0, 19, 5, 4 },
+ { 2, 10, 19, 5, 4 },
+ { 4, 0, 200, 50, 50 },
+ { 4, 123, 323, 50, 50 },
+ { 4, 0, 1201, 300, 301 },
+ };
+
[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)]
+ [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))]
public void IterateRows_OverMinimumPixelsLimit(
int maxDegreeOfParallelism,
int minY,
@@ -26,12 +41,17 @@ namespace SixLabors.ImageSharp.Tests.Helpers
int expectedStepLength,
int expectedLastStepLength)
{
- var parallelSettings = new ParallelExecutionSettings(maxDegreeOfParallelism, 1);
+ var parallelSettings = new ParallelExecutionSettings(
+ maxDegreeOfParallelism,
+ 1,
+ Configuration.Default.MemoryAllocator);
var rectangle = new Rectangle(0, minY, 10, maxY - minY);
int actualNumberOfSteps = 0;
- ParallelHelper.IterateRows(rectangle, parallelSettings,
+ ParallelHelper.IterateRows(
+ rectangle,
+ parallelSettings,
rows =>
{
Assert.True(rows.Min >= minY);
@@ -46,16 +66,63 @@ 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, 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)]
+ [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))]
+ public void IterateRowsWithTempBuffer_OverMinimumPixelsLimit(
+ int maxDegreeOfParallelism,
+ int minY,
+ int maxY,
+ int expectedStepLength,
+ int expectedLastStepLength)
+ {
+ var parallelSettings = new ParallelExecutionSettings(
+ maxDegreeOfParallelism,
+ 1,
+ Configuration.Default.MemoryAllocator);
+
+ var rectangle = new Rectangle(0, minY, 10, maxY - minY);
+
+ var bufferHashes = new ConcurrentBag();
+
+ int actualNumberOfSteps = 0;
+ ParallelHelper.IterateRowsWithTempBuffer(
+ rectangle,
+ parallelSettings,
+ (RowInterval rows, Memory buffer) =>
+ {
+ Assert.True(rows.Min >= minY);
+ Assert.True(rows.Max <= maxY);
+
+ bufferHashes.Add(buffer.GetHashCode());
+
+ int step = rows.Max - rows.Min;
+ int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength;
+
+ Interlocked.Increment(ref actualNumberOfSteps);
+ Assert.Equal(expected, step);
+ });
+
+ Assert.Equal(maxDegreeOfParallelism, actualNumberOfSteps);
+
+ int numberOfDifferentBuffers = bufferHashes.Distinct().Count();
+ Assert.Equal(actualNumberOfSteps, numberOfDifferentBuffers);
+ }
+
+ public static TheoryData IterateRows_WithEffectiveMinimumPixelsLimit_Data =
+ new TheoryData()
+ {
+ { 2, 200, 50, 2, 1, -1, 2 },
+ { 2, 200, 200, 1, 1, -1, 1 },
+ { 4, 200, 100, 4, 2, 2, 2 },
+ { 4, 300, 100, 8, 3, 3, 2 },
+ { 2, 5000, 1, 4500, 1, -1, 4500 },
+ { 2, 5000, 1, 5000, 1, -1, 5000 },
+ { 2, 5000, 1, 5001, 2, 2501, 2500 },
+ };
+
+ [Theory]
+ [MemberData(nameof(IterateRows_WithEffectiveMinimumPixelsLimit_Data))]
public void IterateRows_WithEffectiveMinimumPixelsLimit(
int maxDegreeOfParallelism,
int minimumPixelsProcessedPerTask,
@@ -65,12 +132,18 @@ namespace SixLabors.ImageSharp.Tests.Helpers
int expectedStepLength,
int expectedLastStepLength)
{
- var parallelSettings = new ParallelExecutionSettings(maxDegreeOfParallelism, minimumPixelsProcessedPerTask);
+ var parallelSettings = new ParallelExecutionSettings(
+ maxDegreeOfParallelism,
+ minimumPixelsProcessedPerTask,
+ Configuration.Default.MemoryAllocator);
var rectangle = new Rectangle(0, 0, width, height);
int actualNumberOfSteps = 0;
- ParallelHelper.IterateRows(rectangle, parallelSettings,
+
+ ParallelHelper.IterateRows(
+ rectangle,
+ parallelSettings,
rows =>
{
Assert.True(rows.Min >= 0);
@@ -85,5 +158,42 @@ namespace SixLabors.ImageSharp.Tests.Helpers
Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps);
}
+
+ [Theory]
+ [MemberData(nameof(IterateRows_WithEffectiveMinimumPixelsLimit_Data))]
+ public void IterateRowsWithTempBuffer_WithEffectiveMinimumPixelsLimit(
+ int maxDegreeOfParallelism,
+ int minimumPixelsProcessedPerTask,
+ int width,
+ int height,
+ int expectedNumberOfSteps,
+ int expectedStepLength,
+ int expectedLastStepLength)
+ {
+ var parallelSettings = new ParallelExecutionSettings(
+ maxDegreeOfParallelism,
+ minimumPixelsProcessedPerTask,
+ Configuration.Default.MemoryAllocator);
+
+ var rectangle = new Rectangle(0, 0, width, height);
+
+ int actualNumberOfSteps = 0;
+ ParallelHelper.IterateRowsWithTempBuffer(
+ rectangle,
+ parallelSettings,
+ (RowInterval rows, Memory buffer) =>
+ {
+ Assert.True(rows.Min >= 0);
+ Assert.True(rows.Max <= height);
+
+ int step = rows.Max - rows.Min;
+ int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength;
+
+ Interlocked.Increment(ref actualNumberOfSteps);
+ Assert.Equal(expected, step);
+ });
+
+ Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps);
+ }
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs b/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs
new file mode 100644
index 000000000..f092da708
--- /dev/null
+++ b/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Runtime.CompilerServices;
+
+using SixLabors.ImageSharp.Memory;
+using SixLabors.Memory;
+
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Helpers
+{
+ public class RowIntervalTests
+ {
+ [Theory]
+ [InlineData(10, 20, 5, 10)]
+ [InlineData(1, 10, 0, 10)]
+ [InlineData(1, 10, 5, 8)]
+ [InlineData(1, 1, 0, 1)]
+ [InlineData(10, 20, 9, 10)]
+ [InlineData(10, 20, 0, 1)]
+ public void GetMultiRowSpan(int width, int height, int min, int max)
+ {
+ using (Buffer2D buffer = Configuration.Default.MemoryAllocator.Allocate2D(width, height))
+ {
+ var rows = new RowInterval(min, max);
+
+ Span span = buffer.GetMultiRowSpan(rows);
+
+ ref int expected0 = ref buffer.Span[min * width];
+ int expectedLength = (max - min) * width;
+
+ ref int actual0 = ref span[0];
+
+ Assert.Equal(span.Length, expectedLength);
+ Assert.True(Unsafe.AreSame(ref expected0, ref actual0));
+ }
+ }
+ }
+}
\ No newline at end of file