-
-
Notifications
You must be signed in to change notification settings - Fork 893
Remove Closure Allocations when Iterating Rows in Parallel #1107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 57 commits
aaa5bb7
b6d8375
4523366
463f0a9
abb064f
e3a7bb4
62890f8
7f19fb2
01f2c05
e40731d
3b3e6ab
079e2a6
2f83149
6298c78
c5ed869
58418b4
1564149
d844f8a
2bc5d82
b77a27c
d5d50e9
f49d1b6
6d4d9a6
d39a057
c6c6b9b
e9e461e
d7bce16
c01889f
4cf5920
57acad2
70f0e9a
e771c50
5083818
57e03ae
add9613
d274142
6b9d344
dd7bd7b
b77dcfa
a0ad4ce
d6fb30e
dd4285d
3da200d
b545ea0
1835475
fcf7c44
8540f83
89e3898
2b00433
7a8edff
7609a53
c946849
b247bac
c5166c9
3cfe575
49d0215
eefa57e
4b7718e
b5d055f
c66dd0c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| // Copyright (c) Six Labors and contributors. | ||
| // Licensed under the Apache License, Version 2.0. | ||
|
|
||
| using System; | ||
| using System.Runtime.CompilerServices; | ||
| using SixLabors.ImageSharp.Memory; | ||
|
|
||
| namespace SixLabors.ImageSharp.Advanced | ||
| { | ||
| /// <summary> | ||
| /// Defines the contract for an action that operates on a row interval. | ||
| /// </summary> | ||
| public interface IRowIntervalOperation | ||
| { | ||
| /// <summary> | ||
| /// Invokes the method passing the row interval. | ||
| /// </summary> | ||
| /// <param name="rows">The row interval.</param> | ||
| void Invoke(in RowInterval rows); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| // 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 | ||
| { | ||
| /// <summary> | ||
| /// Defines the contract for an action that operates on a row interval with a temporary buffer. | ||
| /// </summary> | ||
| /// <typeparam name="TBuffer">The type of buffer elements.</typeparam> | ||
| public interface IRowIntervalOperation<TBuffer> | ||
| where TBuffer : unmanaged | ||
| { | ||
| /// <summary> | ||
| /// Invokes the method passing the row interval and a buffer. | ||
| /// </summary> | ||
| /// <param name="rows">The row interval.</param> | ||
| /// <param name="span">The contiguous region of memory.</param> | ||
| void Invoke(in RowInterval rows, Span<TBuffer> span); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| // Copyright (c) Six Labors and contributors. | ||
| // Licensed under the Apache License, Version 2.0. | ||
|
|
||
| using System; | ||
| using System.Buffers; | ||
| using System.Runtime.CompilerServices; | ||
| using System.Threading.Tasks; | ||
| using SixLabors.ImageSharp.Memory; | ||
|
|
||
| namespace SixLabors.ImageSharp.Advanced | ||
| { | ||
| /// <content> | ||
| /// Utility methods for batched processing of pixel row intervals. | ||
| /// Parallel execution is optimized for image processing based on values defined | ||
| /// <see cref="ParallelExecutionSettings"/> or <see cref="Configuration"/>. | ||
| /// Using this class is preferred over direct usage of <see cref="Parallel"/> utility methods. | ||
| /// </content> | ||
| public static partial class ParallelRowIterator | ||
| { | ||
| private readonly struct WrappingRowIntervalInfo | ||
| { | ||
| public readonly int MinY; | ||
| public readonly int MaxY; | ||
| public readonly int StepY; | ||
| public readonly int MaxX; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good to me. |
||
|
|
||
| public WrappingRowIntervalInfo(int minY, int maxY, int stepY) | ||
| : this(minY, maxY, stepY, 0) | ||
| { | ||
| } | ||
|
|
||
| public WrappingRowIntervalInfo(int minY, int maxY, int stepY, int maxX) | ||
| { | ||
| this.MinY = minY; | ||
| this.MaxY = maxY; | ||
| this.StepY = stepY; | ||
| this.MaxX = maxX; | ||
| } | ||
| } | ||
|
|
||
| private readonly struct WrappingRowIntervalOperation<T> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Haha, yeah no problem. |
||
| where T : struct, IRowIntervalOperation | ||
| { | ||
| private readonly WrappingRowIntervalInfo info; | ||
| private readonly T operation; | ||
|
|
||
| [MethodImpl(InliningOptions.ShortMethod)] | ||
| public WrappingRowIntervalOperation(in WrappingRowIntervalInfo info, in T operation) | ||
| { | ||
| this.info = info; | ||
| this.operation = operation; | ||
| } | ||
|
|
||
| [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); | ||
|
|
||
| // Skip the safety copy when invoking a potentially impure method on a readonly field | ||
| Unsafe.AsRef(this.operation).Invoke(in rows); | ||
| } | ||
| } | ||
|
|
||
| private readonly struct WrappingRowIntervalBufferOperation<T, TBuffer> | ||
| where T : struct, IRowIntervalOperation<TBuffer> | ||
| where TBuffer : unmanaged | ||
| { | ||
| private readonly WrappingRowIntervalInfo info; | ||
| private readonly MemoryAllocator allocator; | ||
| private readonly T operation; | ||
|
|
||
| [MethodImpl(InliningOptions.ShortMethod)] | ||
| public WrappingRowIntervalBufferOperation( | ||
| in WrappingRowIntervalInfo info, | ||
| MemoryAllocator allocator, | ||
| in T operation) | ||
| { | ||
| this.info = info; | ||
| this.allocator = allocator; | ||
| this.operation = operation; | ||
| } | ||
|
|
||
| [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<TBuffer> buffer = this.allocator.Allocate<TBuffer>(this.info.MaxX); | ||
|
|
||
| Unsafe.AsRef(this.operation).Invoke(in rows, buffer.Memory.Span); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| // Copyright (c) Six Labors and contributors. | ||
| // Licensed under the Apache License, Version 2.0. | ||
|
|
||
| using System; | ||
| using System.Buffers; | ||
| using System.Runtime.CompilerServices; | ||
| using System.Threading.Tasks; | ||
| using SixLabors.ImageSharp.Memory; | ||
|
|
||
| namespace SixLabors.ImageSharp.Advanced | ||
| { | ||
| /// <summary> | ||
| /// Utility methods for batched processing of pixel row intervals. | ||
| /// Parallel execution is optimized for image processing based on values defined | ||
| /// <see cref="ParallelExecutionSettings"/> or <see cref="Configuration"/>. | ||
| /// Using this class is preferred over direct usage of <see cref="Parallel"/> utility methods. | ||
| /// </summary> | ||
| public static partial class ParallelRowIterator | ||
| { | ||
| /// <summary> | ||
| /// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s. | ||
| /// </summary> | ||
| /// <typeparam name="T">The type of row operation to perform.</typeparam> | ||
| /// <param name="configuration">The <see cref="Configuration"/> to get the parallel settings from.</param> | ||
| /// <param name="rectangle">The <see cref="Rectangle"/>.</param> | ||
| /// <param name="operation">The operation defining the iteration logic on a single <see cref="RowInterval"/>.</param> | ||
| [MethodImpl(InliningOptions.ShortMethod)] | ||
| public static void IterateRows<T>(Configuration configuration, Rectangle rectangle, in T operation) | ||
| where T : struct, IRowIntervalOperation | ||
| { | ||
| var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); | ||
| IterateRows(rectangle, in parallelSettings, in operation); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s. | ||
| /// </summary> | ||
| /// <typeparam name="T">The type of row operation to perform.</typeparam> | ||
| /// <param name="rectangle">The <see cref="Rectangle"/>.</param> | ||
| /// <param name="parallelSettings">The <see cref="ParallelExecutionSettings"/>.</param> | ||
| /// <param name="operation">The operation defining the iteration logic on a single <see cref="RowInterval"/>.</param> | ||
| public static void IterateRows<T>( | ||
| Rectangle rectangle, | ||
| in ParallelExecutionSettings parallelSettings, | ||
| in T operation) | ||
| where T : struct, IRowIntervalOperation | ||
| { | ||
| ValidateRectangle(rectangle); | ||
|
|
||
| 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(top, bottom); | ||
| Unsafe.AsRef(operation).Invoke(in rows); | ||
| return; | ||
| } | ||
|
|
||
| int verticalStep = DivideCeil(rectangle.Height, numOfSteps); | ||
| var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; | ||
| var info = new WrappingRowIntervalInfo(top, bottom, verticalStep); | ||
| var wrappingOperation = new WrappingRowIntervalOperation<T>(in info, in operation); | ||
|
|
||
| Parallel.For( | ||
| 0, | ||
| numOfSteps, | ||
| parallelOptions, | ||
| wrappingOperation.Invoke); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s | ||
| /// instantiating a temporary buffer for each <paramref name="operation"/> invocation. | ||
| /// </summary> | ||
| /// <typeparam name="T">The type of row operation to perform.</typeparam> | ||
| /// <typeparam name="TBuffer">The type of buffer elements.</typeparam> | ||
| /// <param name="configuration">The <see cref="Configuration"/> to get the parallel settings from.</param> | ||
| /// <param name="rectangle">The <see cref="Rectangle"/>.</param> | ||
| /// <param name="operation">The operation defining the iteration logic on a single <see cref="RowInterval"/>.</param> | ||
| public static void IterateRows<T, TBuffer>(Configuration configuration, Rectangle rectangle, in T operation) | ||
| where T : struct, IRowIntervalOperation<TBuffer> | ||
| where TBuffer : unmanaged | ||
| { | ||
| var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); | ||
| IterateRows<T, TBuffer>(rectangle, in parallelSettings, in operation); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s | ||
| /// instantiating a temporary buffer for each <paramref name="operation"/> invocation. | ||
| /// </summary> | ||
| /// <typeparam name="T">The type of row operation to perform.</typeparam> | ||
| /// <typeparam name="TBuffer">The type of buffer elements.</typeparam> | ||
| /// <param name="rectangle">The <see cref="Rectangle"/>.</param> | ||
| /// <param name="parallelSettings">The <see cref="ParallelExecutionSettings"/>.</param> | ||
| /// <param name="operation">The operation defining the iteration logic on a single <see cref="RowInterval"/>.</param> | ||
| public static void IterateRows<T, TBuffer>( | ||
| Rectangle rectangle, | ||
| in ParallelExecutionSettings parallelSettings, | ||
| in T operation) | ||
| where T : struct, IRowIntervalOperation<TBuffer> | ||
| where TBuffer : unmanaged | ||
| { | ||
| ValidateRectangle(rectangle); | ||
|
|
||
| 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); | ||
| MemoryAllocator allocator = parallelSettings.MemoryAllocator; | ||
|
|
||
| // Avoid TPL overhead in this trivial case: | ||
| if (numOfSteps == 1) | ||
| { | ||
| var rows = new RowInterval(top, bottom); | ||
| using (IMemoryOwner<TBuffer> buffer = allocator.Allocate<TBuffer>(width)) | ||
| { | ||
| Unsafe.AsRef(operation).Invoke(rows, buffer.Memory.Span); | ||
| } | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| int verticalStep = DivideCeil(height, numOfSteps); | ||
| var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; | ||
| var info = new WrappingRowIntervalInfo(top, bottom, verticalStep, width); | ||
| var wrappingOperation = new WrappingRowIntervalBufferOperation<T, TBuffer>(in info, allocator, in operation); | ||
|
|
||
| Parallel.For( | ||
| 0, | ||
| numOfSteps, | ||
| parallelOptions, | ||
| wrappingOperation.Invoke); | ||
| } | ||
|
|
||
| [MethodImpl(InliningOptions.ShortMethod)] | ||
| private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor); | ||
|
|
||
| private static void ValidateRectangle(Rectangle rectangle) | ||
| { | ||
| Guard.MustBeGreaterThan( | ||
| rectangle.Width, | ||
| 0, | ||
| $"{nameof(rectangle)}.{nameof(rectangle.Width)}"); | ||
|
|
||
| Guard.MustBeGreaterThan( | ||
| rectangle.Height, | ||
| 0, | ||
| $"{nameof(rectangle)}.{nameof(rectangle.Height)}"); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This deserves a better name.
IterationParametersmaybe?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine with that, will update.