// 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.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
{
///
/// Executes a specified action in an optimized parallel loop over the input data.
///
/// The type of items to iterate over.
/// The type of action (implementing of ) to invoke over each item.
/// The input representing the data to process.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ForEach(ReadOnlyMemory2D memory)
where TAction : struct, IInAction
{
ForEach(memory, default(TAction), 1);
}
///
/// Executes a specified action in an optimized parallel loop over the input data.
///
/// The type of items to iterate over.
/// The type of action (implementing of ) to invoke over each item.
/// The input representing the data to process.
///
/// 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 ForEach(ReadOnlyMemory2D memory, int minimumActionsPerThread)
where TAction : struct, IInAction
{
ForEach(memory, default(TAction), minimumActionsPerThread);
}
///
/// Executes a specified action in an optimized parallel loop over the input data.
///
/// The type of items to iterate over.
/// The type of action (implementing of ) to invoke over each item.
/// The input representing the data to process.
/// The instance representing the action to invoke.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ForEach(ReadOnlyMemory2D memory, in TAction action)
where TAction : struct, IInAction
{
ForEach(memory, action, 1);
}
///
/// Executes a specified action in an optimized parallel loop over the input data.
///
/// The type of items to iterate over.
/// The type of action (implementing of ) to invoke over each item.
/// The input representing the data to process.
/// 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 ForEach(ReadOnlyMemory2D memory, in TAction action, int minimumActionsPerThread)
where TAction : struct, IInAction
{
if (minimumActionsPerThread <= 0)
{
ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerThread();
}
if (memory.IsEmpty)
{
return;
}
nint maxBatches = 1 + ((memory.Length - 1) / minimumActionsPerThread);
nint clipBatches = maxBatches <= memory.Height ? maxBatches : memory.Height;
int cores = Environment.ProcessorCount;
int numBatches = (int)(clipBatches <= cores ? clipBatches : cores);
int batchHeight = 1 + ((memory.Height - 1) / numBatches);
InActionInvokerWithReadOnlyMemory2D actionInvoker = new(batchHeight, memory, action);
// Skip the parallel invocation when possible
if (numBatches == 1)
{
actionInvoker.Invoke(0);
return;
}
// 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 InActionInvokerWithReadOnlyMemory2D
where TAction : struct, IInAction
{
private readonly int batchHeight;
private readonly ReadOnlyMemory2D memory;
private readonly TAction action;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public InActionInvokerWithReadOnlyMemory2D(
int batchHeight,
ReadOnlyMemory2D memory,
in TAction action)
{
this.batchHeight = batchHeight;
this.memory = memory;
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 lowY = i * this.batchHeight;
nint highY = lowY + this.batchHeight;
int stopY = (int)(highY <= this.memory.Height ? highY : this.memory.Height);
int width = this.memory.Width;
ReadOnlySpan2D span = this.memory.Span;
for (int y = lowY; y < stopY; y++)
{
ref TItem rStart = ref span.DangerousGetReferenceAt(y, 0);
ref TItem rEnd = ref Unsafe.Add(ref rStart, width);
while (Unsafe.IsAddressLessThan(ref rStart, ref rEnd))
{
Unsafe.AsRef(this.action).Invoke(in rStart);
rStart = ref Unsafe.Add(ref rStart, 1);
}
}
}
}
}