From 67334c7d2b5ef59acb684fe34d67f7d3135ccae6 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 15 Sep 2018 19:43:02 +0200 Subject: [PATCH] implemented IterateRowsWithTempBuffer --- .../ParallelExecutionSettings.cs | 11 +- .../Common/ParallelUtils/ParallelHelper.cs | 72 +++++++-- src/ImageSharp/Memory/Buffer2DExtensions.cs | 9 ++ .../ParallelUtils => Memory}/RowInterval.cs | 5 +- .../Helpers/ParallelHelperTests.cs | 148 +++++++++++++++--- .../Helpers/RowIntervalTests.cs | 38 +++++ 6 files changed, 246 insertions(+), 37 deletions(-) rename src/ImageSharp/{Common/ParallelUtils => Memory}/RowInterval.cs (89%) create mode 100644 tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs 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