diff --git a/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction.cs b/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction.cs index b178434ce..830fcf736 100644 --- a/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction.cs +++ b/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction.cs @@ -8,7 +8,7 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Advanced.ParallelUtils { /// - /// Defines the contract for. + /// Defines the contract for an action that operates on a row interval. /// public interface IRowIntervalAction { @@ -21,15 +21,22 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils internal readonly struct WrappingRowIntervalInfo { - public readonly int Min; - public readonly int Max; - public readonly int Step; + public readonly int MinY; + public readonly int MaxY; + public readonly int StepY; + public readonly int MaxX; - public WrappingRowIntervalInfo(int min, int max, int step) + public WrappingRowIntervalInfo(int minY, int maxY, int stepY) + : this(minY, maxY, stepY, 0) { - this.Min = min; - this.Max = max; - this.Step = step; + } + + public WrappingRowIntervalInfo(int minY, int maxY, int stepY, int maxX) + { + this.MinY = minY; + this.MaxY = maxY; + this.StepY = stepY; + this.MaxX = maxX; } } @@ -39,7 +46,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils private readonly WrappingRowIntervalInfo info; private readonly T action; - public WrappingRowIntervalAction(in WrappingRowIntervalInfo info, ref T action) + public WrappingRowIntervalAction(in WrappingRowIntervalInfo info, in T action) { this.info = info; this.action = action; @@ -48,14 +55,14 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int i) { - int yMin = this.info.Min + (i * this.info.Step); + int yMin = this.info.MinY + (i * this.info.StepY); - if (yMin >= this.info.Max) + if (yMin >= this.info.MaxY) { return; } - int yMax = Math.Min(yMin + this.info.Step, this.info.Max); + int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY); var rows = new RowInterval(yMin, yMax); diff --git a/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction{TBuffer}.cs b/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction{TBuffer}.cs new file mode 100644 index 000000000..c0899ad3a --- /dev/null +++ b/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction{TBuffer}.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Advanced.ParallelUtils +{ + /// + /// Defines the contract for an action that operates on a row interval with a temporary buffer. + /// + /// The type of buffer elements. + public interface IRowIntervalAction + where TBuffer : unmanaged + { + /// + /// Invokes the method passing the row interval and a buffer. + /// + /// The row interval. + /// The contiguous region of memory. + void Invoke(in RowInterval rows, Memory memory); + } + + internal readonly struct WrappingRowIntervalAction : IRowIntervalAction + where T : struct, IRowIntervalAction + where TBuffer : unmanaged + { + private readonly WrappingRowIntervalInfo info; + private readonly MemoryAllocator allocator; + private readonly T action; + + public WrappingRowIntervalAction( + in WrappingRowIntervalInfo info, + MemoryAllocator allocator, + in T action) + { + this.info = info; + this.allocator = allocator; + this.action = action; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int i) + { + int yMin = this.info.MinY + (i * this.info.StepY); + + if (yMin >= this.info.MaxY) + { + return; + } + + int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY); + + var rows = new RowInterval(yMin, yMax); + + using (IMemoryOwner buffer = this.allocator.Allocate(this.info.MaxX)) + { + this.Invoke(in rows, buffer.Memory); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows, Memory memory) => this.action.Invoke(in rows, memory); + } +} diff --git a/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs b/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs index 58f3169d1..92c2ff20f 100644 --- a/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs +++ b/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs @@ -21,14 +21,16 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils /// /// Iterate through the rows of a rectangle in optimized batches defined by -s. /// + /// The type of row action to perform. /// The . /// The to get the parallel settings from. /// The method body defining the iteration logic on a single . - public static void IterateRows(Rectangle rectangle, Configuration configuration, Action body) + [MethodImpl(InliningOptions.ShortMethod)] + public static void IterateRows(Rectangle rectangle, Configuration configuration, in T body) + where T : struct, IRowIntervalAction { var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); - - IterateRows(rectangle, in parallelSettings, body); + IterateRows(rectangle, in parallelSettings, in body); } /// @@ -36,52 +38,120 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils /// /// The type of row action to perform. /// The . - /// The to get the parallel settings from. + /// The . /// The method body defining the iteration logic on a single . - public static void IterateRowsFast(Rectangle rectangle, Configuration configuration, ref T body) - where T : struct, IRowIntervalAction - { - var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); - - IterateRowsFast(rectangle, in parallelSettings, ref body); - } - - internal static void IterateRowsFast( + public static void IterateRows( Rectangle rectangle, in ParallelExecutionSettings parallelSettings, - ref T body) + in T body) where T : struct, IRowIntervalAction { ValidateRectangle(rectangle); - int maxSteps = DivideCeil( - rectangle.Width * rectangle.Height, - parallelSettings.MinimumPixelsProcessedPerTask); + int top = rectangle.Top; + int bottom = rectangle.Bottom; + int width = rectangle.Width; + int height = rectangle.Height; + int maxSteps = DivideCeil(width * 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); + var rows = new RowInterval(top, bottom); body.Invoke(in rows); return; } int verticalStep = DivideCeil(rectangle.Height, numOfSteps); - var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep); + var rowAction = new WrappingRowIntervalAction(in rowInfo, in body); + + Parallel.For( + 0, + numOfSteps, + parallelOptions, + i => rowAction.Invoke(i)); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s + /// instantiating a temporary buffer for each invocation. + /// + /// The type of row action to perform. + /// The type of buffer elements. + /// The . + /// The to get the parallel settings from. + /// The method body defining the iteration logic on a single . + public static void IterateRows(Rectangle rectangle, Configuration configuration, in T body) + where T : struct, IRowIntervalAction + where TBuffer : unmanaged + { + var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); + IterateRows(rectangle, in parallelSettings, in body); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s + /// instantiating a temporary buffer for each invocation. + /// + internal static void IterateRows( + Rectangle rectangle, + in ParallelExecutionSettings parallelSettings, + in T body) + where T : struct, IRowIntervalAction + where TBuffer : unmanaged + { + ValidateRectangle(rectangle); int top = rectangle.Top; int bottom = rectangle.Bottom; - var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep); - var rowAction = new WrappingRowIntervalAction(in rowInfo, ref body); + int width = rectangle.Width; + int height = rectangle.Height; + + int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + MemoryAllocator allocator = parallelSettings.MemoryAllocator; + + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + var rows = new RowInterval(top, bottom); + using (IMemoryOwner buffer = allocator.Allocate(width)) + { + body.Invoke(rows, buffer.Memory); + } + + return; + } + + int verticalStep = DivideCeil(height, numOfSteps); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep, width); + var rowAction = new WrappingRowIntervalAction(in rowInfo, allocator, in body); Parallel.For( 0, numOfSteps, parallelOptions, - i => rowAction.Invoke(i)); + i => + rowAction.Invoke(i)); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s. + /// + /// The . + /// The to get the parallel settings from. + /// The method body defining the iteration logic on a single . + // [Obsolete("Use non-allocating generic versions instead.")] + public static void IterateRows(Rectangle rectangle, Configuration configuration, Action body) + { + var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); + + IterateRows(rectangle, in parallelSettings, body); } /// @@ -90,10 +160,11 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils /// The . /// The . /// The method body defining the iteration logic on a single . + // [Obsolete("Use non-allocating generic versions instead.")] public static void IterateRows( - Rectangle rectangle, - in ParallelExecutionSettings parallelSettings, - Action body) + Rectangle rectangle, + in ParallelExecutionSettings parallelSettings, + Action body) { ValidateRectangle(rectangle); @@ -143,6 +214,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils /// Iterate through the rows of a rectangle in optimized batches defined by -s /// instantiating a temporary buffer for each invocation. /// + // [Obsolete("Use non-allocating generic versions instead.")] internal static void IterateRowsWithTempBuffer( Rectangle rectangle, in ParallelExecutionSettings parallelSettings, @@ -204,6 +276,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils /// Iterate through the rows of a rectangle in optimized batches defined by -s /// instantiating a temporary buffer for each invocation. /// + // [Obsolete("Use non-allocating generic versions instead.")] internal static void IterateRowsWithTempBuffer( Rectangle rectangle, Configuration configuration, @@ -213,7 +286,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils IterateRowsWithTempBuffer(rectangle, ParallelExecutionSettings.FromConfiguration(configuration), body); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor); private static void ValidateRectangle(Rectangle rectangle) diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index 59ef75b6e..8d3fec97d 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -54,10 +54,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms var rowAction = new RowAction(ref bounds, source, destination); - ParallelHelper.IterateRowsFast( + ParallelHelper.IterateRows( bounds, in parallelSettings, - ref rowAction); + in rowAction); } private readonly struct RowAction : IRowIntervalAction