Skip to content
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

Remove Closure Allocations when Iterating Rows in Parallel #1107

Merged
merged 60 commits into from
Feb 12, 2020

Conversation

JimBobSquarePants
Copy link
Member

@JimBobSquarePants JimBobSquarePants commented Feb 6, 2020

Prerequisites

  • I have written a descriptive pull-request title
  • I have verified that there are no overlapping pull-requests open
  • I have verified that I am following matches the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules 👮.
  • I have provided test coverage for my change (where applicable)

Description

The way ImageSharp currently handles work-to-be-executed in parallel, at the simplest level, as a Action<RowInterval>. Any state necessary for the operation must therefore be provided as closures, incurring heap allocations.

By utilizing a trick to exploit the reification in C# generics it's possible completely get rid of callvirt instructions when invoking a delegate passed as argument which, in turn, allows the compiler to inline the input delegate. This removes the closure allocations and importantly removes the risk of developers inadvertently passing this as an allocation.

We can perform this optimization by updating the current ParallelHelper.IterateRows methods and variants to accept a readonly struct implementing an interface instead the current action. This optimization technique is already utilized within the IImageProcessor<TPixel> API.

The interface pattern is as follows.

/// <summary>
/// Defines the contract for an action that operates on a row interval.
/// </summary>
public interface IRowIntervalAction
{
    /// <summary>
    /// Invokes the method passing the row interval.
    /// </summary>
    /// <param name="rows">The row interval.</param>
    void Invoke(in RowInterval rows);
}

/// <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 IRowIntervalAction<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="memory">The contiguous region of memory.</param>
    void Invoke(in RowInterval rows, Memory<TBuffer> memory);
}

And the methods as follows.

/// <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 action to perform.</typeparam>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="parallelSettings">The <see cref="ParallelExecutionSettings"/>.</param>
/// <param name="body">The method body defining the iteration logic on a single <see cref="RowInterval"/>.</param>
public static void IterateRows<T>(
    Rectangle rectangle,
    in ParallelExecutionSettings parallelSettings,
    in T body)
    where T : struct, IRowIntervalAction

/// <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="body"/> invocation.
/// </summary>
internal static void IterateRows<T, TBuffer>(
    Rectangle rectangle,
    in ParallelExecutionSettings parallelSettings,
    in T body)
    where T : struct, IRowIntervalAction<TBuffer>
    where TBuffer : unmanaged

TODO

  • Define new interfaces and methods
  • Refactor existing processor to use new methods
  • Remove old methods and update tests

@codecov
Copy link

codecov bot commented Feb 6, 2020

Codecov Report

Merging #1107 into master will decrease coverage by 0.18%.
The diff coverage is 94.21%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1107      +/-   ##
==========================================
- Coverage   82.23%   82.04%   -0.19%     
==========================================
  Files         702      700       -2     
  Lines       29163    28883     -280     
  Branches     3297     3286      -11     
==========================================
- Hits        23981    23698     -283     
- Misses       4491     4497       +6     
+ Partials      691      688       -3
Flag Coverage Δ
#unittests 82.04% <94.21%> (-0.19%) ⬇️
Impacted Files Coverage Δ
...lFormats/Utils/Vector4Converters.RgbaCompatible.cs 91.42% <ø> (ø) ⬆️
src/ImageSharp/Primitives/Rectangle.cs 95% <ø> (ø) ⬆️
...c/ImageSharp/Advanced/ParallelExecutionSettings.cs 100% <ø> (ø)
...ageSharp/Common/Extensions/EnumerableExtensions.cs 83.33% <ø> (ø) ⬆️
...ng/Processors/Transforms/Resize/ResizeKernelMap.cs 95.78% <ø> (ø) ⬆️
...ssing/Processors/Transforms/Resize/ResizeWorker.cs 98.7% <ø> (ø) ⬆️
...ocessors/Quantization/QuantizeProcessor{TPixel}.cs 0% <0%> (ø) ⬆️
...ing/Processors/Transforms/CropProcessor{TPixel}.cs 100% <100%> (ø) ⬆️
...ors/Convolution/EdgeDetector2DProcessor{TPixel}.cs 100% <100%> (ø) ⬆️
...ssors/Overlays/BackgroundColorProcessor{TPixel}.cs 100% <100%> (ø) ⬆️
... and 32 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update aaa5bb7...c66dd0c. Read the comment docs.

@JimBobSquarePants JimBobSquarePants changed the title WIP: Remove Closure Allocations when Iterating Rows in Parallel Remove Closure Allocations when Iterating Rows in Parallel Feb 9, 2020
@JimBobSquarePants JimBobSquarePants requested a review from a team February 9, 2020 13:22
@JimBobSquarePants JimBobSquarePants added this to the 1.0.0-rc1 milestone Feb 9, 2020
@JimBobSquarePants
Copy link
Member Author

3... 2... 1... 😉

Copy link
Member

@antonfirsov antonfirsov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few wishes regarding naming of internals I find misleading at the moment, otherwise LGTM.

}
}

private readonly struct WrappingRowIntervalOperation<T>
Copy link
Member

@antonfirsov antonfirsov Feb 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be RowIntervalOperationWrapper<T> instead? Took me a a while to figure out it is exactly the opposite as it's name :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha, yeah no problem.

/// </content>
public static partial class ParallelRowIterator
{
private readonly struct WrappingRowIntervalInfo
Copy link
Member

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. IterationParameters maybe?

Copy link
Member Author

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.

public readonly int MinY;
public readonly int MaxY;
public readonly int StepY;
public readonly int MaxX;
Copy link
Member

@antonfirsov antonfirsov Feb 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be Width instead? First I thought there is a bug, and we are actually allocating a buffer with the size defined by the maximum x coordinate.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me.

@JimBobSquarePants JimBobSquarePants merged commit 1a1c319 into master Feb 12, 2020
@JimBobSquarePants JimBobSquarePants deleted the js/parallel-allocation-experiments branch February 12, 2020 00:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants