From 4bfafc97b59849ba3f4fce47102ac8e4d0d4e9ed Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 15 Sep 2018 18:36:38 +0200 Subject: [PATCH] implemented ParallelHelper.IterateRows() --- src/ImageSharp/Common/Helpers/ParallelFor.cs | 51 ---------- .../ParallelExecutionSettings.cs | 36 +++++++ .../Common/ParallelUtils/ParallelHelper.cs | 95 +++++++++++++++++++ .../Common/ParallelUtils/RowInterval.cs | 35 +++++++ src/ImageSharp/Configuration.cs | 20 ---- src/ImageSharp/ImageSharp.csproj.DotSettings | 3 + .../Helpers/ParallelHelperTests.cs | 42 ++++---- 7 files changed, 193 insertions(+), 89 deletions(-) create mode 100644 src/ImageSharp/Common/ParallelUtils/ParallelExecutionSettings.cs create mode 100644 src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs create mode 100644 src/ImageSharp/Common/ParallelUtils/RowInterval.cs create mode 100644 src/ImageSharp/ImageSharp.csproj.DotSettings 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);