// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace CommunityToolkit.HighPerformance.Helpers;
///
/// Helpers to work with parallel code in a highly optimized manner.
///
public static partial class ParallelHelper
{
#if NETSTANDARD2_1_OR_GREATER
///
/// Executes a specified action in an optimized parallel loop.
///
/// The type of action (implementing ) to invoke for each pair of iteration indices.
/// The value indicating the iteration range for the outer loop.
/// The value indicating the iteration range for the inner loop.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void For2D(Range i, Range j)
where TAction : struct, IAction2D
{
For2D(i, j, default(TAction), 1);
}
///
/// Executes a specified action in an optimized parallel loop.
///
/// The type of action (implementing ) to invoke for each pair of iteration indices.
/// The value indicating the iteration range for the outer loop.
/// The value indicating the iteration range for the inner loop.
///
/// The minimum number of actions to run per individual thread. Set to 1 if all invocations
/// should be parallelized, or to a greater number if each individual invocation is fast
/// enough that it is more efficient to set a lower bound per each running thread.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void For2D(Range i, Range j, int minimumActionsPerThread)
where TAction : struct, IAction2D
{
For2D(i, j, default(TAction), minimumActionsPerThread);
}
///
/// Executes a specified action in an optimized parallel loop.
///
/// The type of action (implementing ) to invoke for each pair of iteration indices.
/// The value indicating the iteration range for the outer loop.
/// The value indicating the iteration range for the inner loop.
/// The instance representing the action to invoke.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void For2D(Range i, Range j, in TAction action)
where TAction : struct, IAction2D
{
For2D(i, j, action, 1);
}
///
/// Executes a specified action in an optimized parallel loop.
///
/// The type of action (implementing ) to invoke for each pair of iteration indices.
/// The value indicating the iteration range for the outer loop.
/// The value indicating the iteration range for the inner loop.
/// The instance representing the action to invoke.
///
/// The minimum number of actions to run per individual thread. Set to 1 if all invocations
/// should be parallelized, or to a greater number if each individual invocation is fast
/// enough that it is more efficient to set a lower bound per each running thread.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void For2D(Range i, Range j, in TAction action, int minimumActionsPerThread)
where TAction : struct, IAction2D
{
if (i.Start.IsFromEnd || i.End.IsFromEnd)
{
ThrowArgumentExceptionForRangeIndexFromEnd(nameof(i));
}
if (j.Start.IsFromEnd || j.End.IsFromEnd)
{
ThrowArgumentExceptionForRangeIndexFromEnd(nameof(j));
}
int top = i.Start.Value;
int bottom = i.End.Value;
int left = j.Start.Value;
int right = j.End.Value;
For2D(top, bottom, left, right, action, minimumActionsPerThread);
}
#endif
///
/// Executes a specified action in an optimized parallel loop.
///
/// The type of action (implementing ) to invoke for each pair of iteration indices.
/// The value indicating the 2D iteration area to use.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void For2D(Rectangle area)
where TAction : struct, IAction2D
{
For2D(area.Top, area.Bottom, area.Left, area.Right, default(TAction), 1);
}
///
/// Executes a specified action in an optimized parallel loop.
///
/// The type of action (implementing ) to invoke for each pair of iteration indices.
/// The value indicating the 2D iteration area to use.
///
/// The minimum number of actions to run per individual thread. Set to 1 if all invocations
/// should be parallelized, or to a greater number if each individual invocation is fast
/// enough that it is more efficient to set a lower bound per each running thread.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void For2D(Rectangle area, int minimumActionsPerThread)
where TAction : struct, IAction2D
{
For2D(area.Top, area.Bottom, area.Left, area.Right, default(TAction), minimumActionsPerThread);
}
///
/// Executes a specified action in an optimized parallel loop.
///
/// The type of action (implementing ) to invoke for each pair of iteration indices.
/// The value indicating the 2D iteration area to use.
/// The instance representing the action to invoke.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void For2D(Rectangle area, in TAction action)
where TAction : struct, IAction2D
{
For2D(area.Top, area.Bottom, area.Left, area.Right, action, 1);
}
///
/// Executes a specified action in an optimized parallel loop.
///
/// The type of action (implementing ) to invoke for each pair of iteration indices.
/// The value indicating the 2D iteration area to use.
/// The instance representing the action to invoke.
///
/// The minimum number of actions to run per individual thread. Set to 1 if all invocations
/// should be parallelized, or to a greater number if each individual invocation is fast
/// enough that it is more efficient to set a lower bound per each running thread.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void For2D(Rectangle area, in TAction action, int minimumActionsPerThread)
where TAction : struct, IAction2D
{
For2D(area.Top, area.Bottom, area.Left, area.Right, action, minimumActionsPerThread);
}
///
/// Executes a specified action in an optimized parallel loop.
///
/// The type of action (implementing ) to invoke for each pair of iteration indices.
/// The starting iteration value for the outer loop.
/// The final iteration value for the outer loop (exclusive).
/// The starting iteration value for the inner loop.
/// The final iteration value for the inner loop (exclusive).
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void For2D(int top, int bottom, int left, int right)
where TAction : struct, IAction2D
{
For2D(top, bottom, left, right, default(TAction), 1);
}
///
/// Executes a specified action in an optimized parallel loop.
///
/// The type of action (implementing ) to invoke for each pair of iteration indices.
/// The starting iteration value for the outer loop.
/// The final iteration value for the outer loop (exclusive).
/// The starting iteration value for the inner loop.
/// The final iteration value for the inner loop (exclusive).
///
/// The minimum number of actions to run per individual thread. Set to 1 if all invocations
/// should be parallelized, or to a greater number if each individual invocation is fast
/// enough that it is more efficient to set a lower bound per each running thread.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void For2D(int top, int bottom, int left, int right, int minimumActionsPerThread)
where TAction : struct, IAction2D
{
For2D(top, bottom, left, right, default(TAction), minimumActionsPerThread);
}
///
/// Executes a specified action in an optimized parallel loop.
///
/// The type of action (implementing ) to invoke for each pair of iteration indices.
/// The starting iteration value for the outer loop.
/// The final iteration value for the outer loop (exclusive).
/// The starting iteration value for the inner loop.
/// The final iteration value for the inner loop (exclusive).
/// The instance representing the action to invoke.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void For2D(int top, int bottom, int left, int right, in TAction action)
where TAction : struct, IAction2D
{
For2D(top, bottom, left, right, action, 1);
}
///
/// Executes a specified action in an optimized parallel loop.
///
/// The type of action (implementing ) to invoke for each pair of iteration indices.
/// The starting iteration value for the outer loop.
/// The final iteration value for the outer loop (exclusive).
/// The starting iteration value for the inner loop.
/// The final iteration value for the inner loop (exclusive).
/// The instance representing the action to invoke.
///
/// The minimum number of actions to run per individual thread. Set to 1 if all invocations
/// should be parallelized, or to a greater number if each individual invocation is fast
/// enough that it is more efficient to set a lower bound per each running thread.
///
public static void For2D(int top, int bottom, int left, int right, in TAction action, int minimumActionsPerThread)
where TAction : struct, IAction2D
{
if (minimumActionsPerThread <= 0)
{
ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerThread();
}
if (top > bottom)
{
ThrowArgumentOutOfRangeExceptionForTopGreaterThanBottom();
}
if (left > right)
{
ThrowArgumentOutOfRangeExceptionForLeftGreaterThanRight();
}
// If either side of the target area is empty, no iterations are performed
if (top == bottom || left == right)
{
return;
}
int height = Math.Abs(top - bottom);
int width = Math.Abs(left - right);
int count = height * width;
int maxBatches = 1 + ((count - 1) / minimumActionsPerThread);
int clipBatches = Math.Min(maxBatches, height);
int cores = Environment.ProcessorCount;
int numBatches = Math.Min(clipBatches, cores);
// Skip the parallel invocation when a single batch is needed
if (numBatches == 1)
{
for (int y = top; y < bottom; y++)
{
for (int x = left; x < right; x++)
{
Unsafe.AsRef(action).Invoke(y, x);
}
}
return;
}
int batchHeight = 1 + ((height - 1) / numBatches);
Action2DInvoker actionInvoker = new(top, bottom, left, right, batchHeight, action);
// Run the batched operations in parallel
_ = Parallel.For(
0,
numBatches,
new ParallelOptions { MaxDegreeOfParallelism = numBatches },
actionInvoker.Invoke);
}
// Wrapping struct acting as explicit closure to execute the processing batches
private readonly struct Action2DInvoker
where TAction : struct, IAction2D
{
private readonly int startY;
private readonly int endY;
private readonly int startX;
private readonly int endX;
private readonly int batchHeight;
private readonly TAction action;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Action2DInvoker(
int startY,
int endY,
int startX,
int endX,
int batchHeight,
in TAction action)
{
this.startY = startY;
this.endY = endY;
this.startX = startX;
this.endX = endX;
this.batchHeight = batchHeight;
this.action = action;
}
///
/// Processes the batch of actions at a specified index
///
/// The index of the batch to process
public void Invoke(int i)
{
int heightOffset = i * this.batchHeight;
int lowY = this.startY + heightOffset;
int highY = lowY + this.batchHeight;
int stopY = Math.Min(highY, this.endY);
for (int y = lowY; y < stopY; y++)
{
for (int x = this.startX; x < this.endX; x++)
{
Unsafe.AsRef(this.action).Invoke(y, x);
}
}
}
}
}
///
/// A contract for actions being executed with two input indices.
///
/// If the method is small enough, it is highly recommended to mark it with .
public interface IAction2D
{
///
/// Executes the action associated with two specified indices.
///
/// The first index for the action to execute.
/// The second index for the action to execute.
void Invoke(int i, int j);
}