mirror of https://github.com/SixLabors/ImageSharp
7 changed files with 193 additions and 89 deletions
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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> |
||||
Loading…
Reference in new issue