Browse Source

implemented ParallelHelper.IterateRows()

pull/710/head
Anton Firszov 8 years ago
parent
commit
4bfafc97b5
  1. 51
      src/ImageSharp/Common/Helpers/ParallelFor.cs
  2. 36
      src/ImageSharp/Common/ParallelUtils/ParallelExecutionSettings.cs
  3. 95
      src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs
  4. 35
      src/ImageSharp/Common/ParallelUtils/RowInterval.cs
  5. 20
      src/ImageSharp/Configuration.cs
  6. 3
      src/ImageSharp/ImageSharp.csproj.DotSettings
  7. 42
      tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs

51
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;
}
/// <summary>
/// Gets the INCLUSIVE minimum
/// </summary>
public int Min { get; }
/// <summary>
/// Gets the EXCLUSIVE maximum
/// </summary>
public int Max { get; }
}
internal static class ParallelHelper
{
public static void IterateRows(in Rectangle rectangle, Configuration configuration, Action<RowInterval> 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);
});
}
}
/// <summary>
/// Utility methods for Parallel.For() execution. Use this instead of raw <see cref="Parallel"/> calls!
/// </summary>

36
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
{
/// <summary>
/// Defines execution settings for methods in <see cref="ParallelHelper"/>.
/// </summary>
internal struct ParallelExecutionSettings
{
public ParallelExecutionSettings(int maxDegreeOfParallelism, int minimumPixelsProcessedPerTask)
{
this.MaxDegreeOfParallelism = maxDegreeOfParallelism;
this.MinimumPixelsProcessedPerTask = minimumPixelsProcessedPerTask;
}
public ParallelExecutionSettings(int maxDegreeOfParallelism)
: this(maxDegreeOfParallelism, 2048)
{
}
/// <summary>
/// Gets the value used for initializing <see cref="ParallelOptions.MaxDegreeOfParallelism"/> when using TPL.
/// </summary>
public int MaxDegreeOfParallelism { get; }
/// <summary>
/// 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.
/// </summary>
public int MinimumPixelsProcessedPerTask { get; }
}
}

95
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
{
/// <summary>
/// Utility methods wrapping Parallel.For() execution optimized for image processing.
/// Use this instead of direct <see cref="Parallel"/> calls!
/// </summary>
internal static class ParallelHelper
{
/// <summary>
/// Get the default <see cref="ParallelExecutionSettings"/> for a <see cref="Configuration"/>
/// </summary>
public static ParallelExecutionSettings GetParallelSettings(this Configuration configuration)
{
return new ParallelExecutionSettings(configuration.MaxDegreeOfParallelism);
}
/// <summary>
/// Gets a span for all the pixels in <paramref name="buffer"/> defined by <paramref name="rows"/>
/// </summary>
public static Span<T> GetMultiRowSpan<T>(this Buffer2D<T> buffer, in RowInterval rows)
where T : struct
{
return buffer.Span.Slice(rows.Min * buffer.Width, rows.Height * buffer.Width);
}
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
/// </summary>
public static void IterateRows(Rectangle rectangle, Configuration configuration, Action<RowInterval> body)
{
ParallelExecutionSettings parallelSettings = configuration.GetParallelSettings();
IterateRows(rectangle, parallelSettings, body);
}
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
/// </summary>
public static void IterateRows(Rectangle rectangle, in ParallelExecutionSettings parallelSettings, Action<RowInterval> 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;
}
}
}

35
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
{
/// <summary>
/// Represents an interval of rows in a <see cref="Rectangle"/> and/or <see cref="Buffer2D{T}"/>
/// </summary>
internal readonly struct RowInterval
{
public RowInterval(int min, int max)
{
this.Min = min;
this.Max = max;
}
/// <summary>
/// Gets the INCLUSIVE minimum
/// </summary>
public int Min { get; }
/// <summary>
/// Gets the EXCLUSIVE maximum
/// </summary>
public int Max { get; }
/// <summary>
/// Gets the difference (<see cref="Max"/> - <see cref="Min"/>)
/// </summary>
public int Height => this.Max - this.Min;
}
}

20
src/ImageSharp/Configuration.cs

@ -29,8 +29,6 @@ namespace SixLabors.ImageSharp
private int maxDegreeOfParallelism = Environment.ProcessorCount;
private int minimumPixelsPerTask = 2048;
/// <summary>
/// Initializes a new instance of the <see cref="Configuration" /> class.
/// </summary>
@ -77,24 +75,6 @@ namespace SixLabors.ImageSharp
}
}
/// <summary>
/// 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.
/// </summary>
public int MinimumPixelsProcessedPerTask
{
get => this.minimumPixelsPerTask;
set
{
if (value <= 0)
{
throw new ArgumentOutOfRangeException(nameof(this.MinimumPixelsProcessedPerTask));
}
this.minimumPixelsPerTask = value;
}
}
/// <summary>
/// Gets the currently registered <see cref="IImageFormat"/>s.
/// </summary>

3
src/ImageSharp/ImageSharp.csproj.DotSettings

@ -0,0 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=common/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=common_005Cexceptions/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

42
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);

Loading…
Cancel
Save