From c338cdcfec6ab3d8cc110df27fd31c0b2452b8a2 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 5 Feb 2020 12:23:28 +1100 Subject: [PATCH] Add row action overload --- .../Advanced/ParallelUtils/IRowAction.cs | 37 +++++++++ .../Advanced/ParallelUtils/ParallelHelper.cs | 76 ++++++++++++++++++- .../Transforms/CropProcessor{TPixel}.cs | 55 +++++++++++--- 3 files changed, 153 insertions(+), 15 deletions(-) create mode 100644 src/ImageSharp/Advanced/ParallelUtils/IRowAction.cs diff --git a/src/ImageSharp/Advanced/ParallelUtils/IRowAction.cs b/src/ImageSharp/Advanced/ParallelUtils/IRowAction.cs new file mode 100644 index 000000000..a0fd2aaf2 --- /dev/null +++ b/src/ImageSharp/Advanced/ParallelUtils/IRowAction.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Advanced.ParallelUtils +{ + /// + /// Defines the contract for. + /// + public interface IRowAction + { + /// + /// Invokes the method passing the row interval. + /// + /// The row interval. + void Invoke(in RowInterval rows); + } + + internal readonly struct WrappingRowAction : IRowAction + where T : struct, IRowAction + { + private readonly T action; + + public WrappingRowAction(ref T action) + { + this.action = action; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + this.action.Invoke(in rows); + } + } +} diff --git a/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs b/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs index ecefadb08..6f9b98d1d 100644 --- a/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs +++ b/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs @@ -28,19 +28,86 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils { var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); - IterateRows(rectangle, parallelSettings, body); + IterateRows(rectangle, in parallelSettings, body); } /// /// Iterate through the rows of a rectangle in optimized batches defined by -s. /// + /// The type of row action to perform. /// The . - /// The . + /// The to get the parallel settings from. /// The method body defining the iteration logic on a single . - public static void IterateRows( + public static void IterateRowsFast(Rectangle rectangle, Configuration configuration, ref T body) + where T : struct, IRowAction + { + var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); + + IterateRowsFast(rectangle, in parallelSettings, ref body); + } + + internal static void IterateRowsFast( Rectangle rectangle, in ParallelExecutionSettings parallelSettings, - Action body) + ref T body) + where T : struct, IRowAction + { + ValidateRectangle(rectangle); + + 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.Invoke(in rows); + return; + } + + int verticalStep = DivideCeil(rectangle.Height, numOfSteps); + + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + + int top = rectangle.Top; + int bottom = rectangle.Bottom; + + var rowAction = new WrappingRowAction(ref body); + + Parallel.For( + 0, + numOfSteps, + parallelOptions, + i => + { + int yMin = top + (i * verticalStep); + + if (yMin >= bottom) + { + return; + } + + int yMax = Math.Min(yMin + verticalStep, bottom); + + var rows = new RowInterval(yMin, yMax); + + rowAction.Invoke(in rows); + }); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s. + /// + /// The . + /// The . + /// The method body defining the iteration logic on a single . + public static void IterateRows( + Rectangle rectangle, + in ParallelExecutionSettings parallelSettings, + Action body) { ValidateRectangle(rectangle); @@ -72,6 +139,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils int yMax = Math.Min(yMin + verticalStep, rectangle.Bottom); var rows = new RowInterval(yMin, yMax); + body(rows); }); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index a286e8fa2..ec78ba171 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -2,8 +2,10 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced.ParallelUtils; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -50,18 +52,49 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(this.Configuration) .MultiplyMinimumPixelsPerTask(4); - ParallelHelper.IterateRows( + // ParallelHelper.IterateRows( + // bounds, + // parallelSettings, + // rows => + // { + // for (int y = rows.Min; y < rows.Max; y++) + // { + // Span sourceRow = source.GetPixelRowSpan(y).Slice(bounds.Left); + // Span targetRow = destination.GetPixelRowSpan(y - bounds.Top); + // sourceRow.Slice(0, bounds.Width).CopyTo(targetRow); + // } + // }); + var rowAction = new RowAction(ref bounds, source, destination); + + ParallelHelper.IterateRowsFast( bounds, - parallelSettings, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRow = source.GetPixelRowSpan(y).Slice(bounds.Left); - Span targetRow = destination.GetPixelRowSpan(y - bounds.Top); - sourceRow.Slice(0, bounds.Width).CopyTo(targetRow); - } - }); + in parallelSettings, + ref rowAction); + } + + private readonly struct RowAction : IRowAction + { + private readonly Rectangle bounds; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + public RowAction(ref Rectangle bounds, ImageFrame source, ImageFrame destination) + { + this.bounds = bounds; + this.source = source; + this.destination = destination; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = this.source.GetPixelRowSpan(y).Slice(this.bounds.Left); + Span targetRow = this.destination.GetPixelRowSpan(y - this.bounds.Top); + sourceRow.Slice(0, this.bounds.Width).CopyTo(targetRow); + } + } } } }