From c094e12ba7f0e1fed7cd476abd73552eef15f937 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 14 Sep 2018 00:16:10 +0200 Subject: [PATCH] ParallelHelper.IterateRows WIP --- src/ImageSharp/Common/Helpers/ParallelFor.cs | 53 ++++++++++++ src/ImageSharp/Configuration.cs | 21 +++++ src/ImageSharp/Memory/Buffer2DExtensions.cs | 11 ++- .../Helpers/ParallelHelperTests.cs | 83 +++++++++++++++++++ 4 files changed, 162 insertions(+), 6 deletions(-) create mode 100644 tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs diff --git a/src/ImageSharp/Common/Helpers/ParallelFor.cs b/src/ImageSharp/Common/Helpers/ParallelFor.cs index 4c14bb6e3..7aa50a9e3 100644 --- a/src/ImageSharp/Common/Helpers/ParallelFor.cs +++ b/src/ImageSharp/Common/Helpers/ParallelFor.cs @@ -1,10 +1,63 @@ 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/Configuration.cs b/src/ImageSharp/Configuration.cs index 1b009bfed..4bd6bd9e5 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -29,6 +29,8 @@ namespace SixLabors.ImageSharp private int maxDegreeOfParallelism = Environment.ProcessorCount; + private int minimumPixelsPerTask = 2048; + /// /// Initializes a new instance of the class. /// @@ -59,6 +61,7 @@ namespace SixLabors.ImageSharp /// /// Gets or sets the maximum number of concurrent tasks enabled in ImageSharp algorithms /// configured with this instance. + /// Initialized with by default. /// public int MaxDegreeOfParallelism { @@ -74,6 +77,24 @@ 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/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index 107457ae7..a9edb9cfb 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -96,15 +96,14 @@ namespace SixLabors.ImageSharp.Memory /// The /// The rectangle subarea /// The - public static BufferArea GetArea(this Buffer2D buffer, Rectangle rectangle) + public static BufferArea GetArea(this Buffer2D buffer, in Rectangle rectangle) where T : struct => new BufferArea(buffer, rectangle); public static BufferArea GetArea(this Buffer2D buffer, int x, int y, int width, int height) - where T : struct - { - var rectangle = new Rectangle(x, y, width, height); - return new BufferArea(buffer, rectangle); - } + where T : struct => new BufferArea(buffer, new Rectangle(x, y, width, height)); + + public static BufferArea GetAreaBetweenRows(this Buffer2D buffer, int minY, int maxY) + where T : struct => new BufferArea(buffer, new Rectangle(0, minY, buffer.Width, maxY - minY)); /// /// Return a to the whole area of 'buffer' diff --git a/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs new file mode 100644 index 000000000..ca904f953 --- /dev/null +++ b/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs @@ -0,0 +1,83 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Threading; + +using SixLabors.ImageSharp.Memory; +using SixLabors.Primitives; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Helpers +{ + public class ParallelHelperTests + { + [Theory] + [InlineData(1, 0, 100, -1, 100)] + [InlineData(2, 0, 9, 5, 4)] + [InlineData(2, 10, 19, 5, 4)] + [InlineData(4, 0, 200, 50, 50)] + [InlineData(4, 123, 323, 50, 50)] + public void IterateRows_OverMinimumPixelsLimit( + int maxDegreeOfParallelism, + int minY, + int maxY, + int expectedStep, + int expectedLastStep) + { + Configuration cfg = Configuration.Default.ShallowCopy(); + cfg.MinimumPixelsProcessedPerTask = 1; + cfg.MaxDegreeOfParallelism = maxDegreeOfParallelism; + + var rectangle = new Rectangle(0, minY, 10, maxY); + + int actualNumberOfSteps = 0; + ParallelHelper.IterateRows(rectangle, cfg, + rows => + { + Assert.True(rows.Min >= minY); + int step = rows.Max - rows.Min; + int expected = rows.Max < maxY ? expectedStep : expectedLastStep; + + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); + }); + + 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, 300, 100, 8, 3, 3, 2)] + public void IterateRows_WithEffectiveMinimumPixelsLimit( + int maxDegreeOfParallelism, + int minimumPixelsProcessedPerTask, + int width, + int height, + int expectedNumberOfSteps, + int expectedStep, + int expectedLastStep) + { + Configuration cfg = Configuration.Default.ShallowCopy(); + cfg.MinimumPixelsProcessedPerTask = minimumPixelsProcessedPerTask; + cfg.MaxDegreeOfParallelism = maxDegreeOfParallelism; + + var rectangle = new Rectangle(0, 0, width, height); + + int actualNumberOfSteps = 0; + ParallelHelper.IterateRows(rectangle, cfg, + rows => + { + int step = rows.Max - rows.Min; + int expected = rows.Max < height ? expectedStep : expectedLastStep; + + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); + }); + + Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); + } + } +} \ No newline at end of file