mirror of
https://github.com/chylex/.NET-Community-Toolkit.git
synced 2024-11-24 07:42:45 +01:00
246 lines
10 KiB
C#
246 lines
10 KiB
C#
// 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;
|
|
|
|
/// <summary>
|
|
/// Helpers to work with parallel code in a highly optimized manner.
|
|
/// </summary>
|
|
public static partial class ParallelHelper
|
|
{
|
|
#if NETSTANDARD2_1_OR_GREATER
|
|
/// <summary>
|
|
/// Executes a specified action in an optimized parallel loop.
|
|
/// </summary>
|
|
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction"/>) to invoke for each iteration index.</typeparam>
|
|
/// <param name="range">The iteration range.</param>
|
|
/// <remarks>None of the bounds of <paramref name="range"/> can start from an end.</remarks>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static void For<TAction>(Range range)
|
|
where TAction : struct, IAction
|
|
{
|
|
For(range, default(TAction), 1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes a specified action in an optimized parallel loop.
|
|
/// </summary>
|
|
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction"/>) to invoke for each iteration index.</typeparam>
|
|
/// <param name="range">The iteration range.</param>
|
|
/// <param name="minimumActionsPerThread">
|
|
/// 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.
|
|
/// </param>
|
|
/// <remarks>None of the bounds of <paramref name="range"/> can start from an end.</remarks>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static void For<TAction>(Range range, int minimumActionsPerThread)
|
|
where TAction : struct, IAction
|
|
{
|
|
For(range, default(TAction), minimumActionsPerThread);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes a specified action in an optimized parallel loop.
|
|
/// </summary>
|
|
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction"/>) to invoke for each iteration index.</typeparam>
|
|
/// <param name="range">The iteration range.</param>
|
|
/// <param name="action">The <typeparamref name="TAction"/> instance representing the action to invoke.</param>
|
|
/// <remarks>None of the bounds of <paramref name="range"/> can start from an end.</remarks>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static void For<TAction>(Range range, in TAction action)
|
|
where TAction : struct, IAction
|
|
{
|
|
For(range, action, 1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes a specified action in an optimized parallel loop.
|
|
/// </summary>
|
|
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction"/>) to invoke for each iteration index.</typeparam>
|
|
/// <param name="range">The iteration range.</param>
|
|
/// <param name="action">The <typeparamref name="TAction"/> instance representing the action to invoke.</param>
|
|
/// <param name="minimumActionsPerThread">
|
|
/// 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.
|
|
/// </param>
|
|
/// <remarks>None of the bounds of <paramref name="range"/> can start from an end.</remarks>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static void For<TAction>(Range range, in TAction action, int minimumActionsPerThread)
|
|
where TAction : struct, IAction
|
|
{
|
|
if (range.Start.IsFromEnd || range.End.IsFromEnd)
|
|
{
|
|
ThrowArgumentExceptionForRangeIndexFromEnd(nameof(range));
|
|
}
|
|
|
|
int start = range.Start.Value;
|
|
int end = range.End.Value;
|
|
|
|
For(start, end, action, minimumActionsPerThread);
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Executes a specified action in an optimized parallel loop.
|
|
/// </summary>
|
|
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction"/>) to invoke for each iteration index.</typeparam>
|
|
/// <param name="start">The starting iteration index.</param>
|
|
/// <param name="end">The final iteration index (exclusive).</param>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static void For<TAction>(int start, int end)
|
|
where TAction : struct, IAction
|
|
{
|
|
For(start, end, default(TAction), 1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes a specified action in an optimized parallel loop.
|
|
/// </summary>
|
|
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction"/>) to invoke for each iteration index.</typeparam>
|
|
/// <param name="start">The starting iteration index.</param>
|
|
/// <param name="end">The final iteration index (exclusive).</param>
|
|
/// <param name="minimumActionsPerThread">
|
|
/// 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.
|
|
/// </param>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static void For<TAction>(int start, int end, int minimumActionsPerThread)
|
|
where TAction : struct, IAction
|
|
{
|
|
For(start, end, default(TAction), minimumActionsPerThread);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes a specified action in an optimized parallel loop.
|
|
/// </summary>
|
|
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction"/>) to invoke for each iteration index.</typeparam>
|
|
/// <param name="start">The starting iteration index.</param>
|
|
/// <param name="end">The final iteration index (exclusive).</param>
|
|
/// <param name="action">The <typeparamref name="TAction"/> instance representing the action to invoke.</param>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static void For<TAction>(int start, int end, in TAction action)
|
|
where TAction : struct, IAction
|
|
{
|
|
For(start, end, action, 1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes a specified action in an optimized parallel loop.
|
|
/// </summary>
|
|
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction"/>) to invoke for each iteration index.</typeparam>
|
|
/// <param name="start">The starting iteration index.</param>
|
|
/// <param name="end">The final iteration index (exclusive).</param>
|
|
/// <param name="action">The <typeparamref name="TAction"/> instance representing the action to invoke.</param>
|
|
/// <param name="minimumActionsPerThread">
|
|
/// 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.
|
|
/// </param>
|
|
public static void For<TAction>(int start, int end, in TAction action, int minimumActionsPerThread)
|
|
where TAction : struct, IAction
|
|
{
|
|
if (minimumActionsPerThread <= 0)
|
|
{
|
|
ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerThread();
|
|
}
|
|
|
|
if (start > end)
|
|
{
|
|
ThrowArgumentOutOfRangeExceptionForStartGreaterThanEnd();
|
|
}
|
|
|
|
if (start == end)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int count = Math.Abs(start - end);
|
|
int maxBatches = 1 + ((count - 1) / minimumActionsPerThread);
|
|
int cores = Environment.ProcessorCount;
|
|
int numBatches = Math.Min(maxBatches, cores);
|
|
|
|
// Skip the parallel invocation when a single batch is needed
|
|
if (numBatches == 1)
|
|
{
|
|
for (int i = start; i < end; i++)
|
|
{
|
|
Unsafe.AsRef(action).Invoke(i);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
int batchSize = 1 + ((count - 1) / numBatches);
|
|
|
|
ActionInvoker<TAction> actionInvoker = new(start, end, batchSize, 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 ActionInvoker<TAction>
|
|
where TAction : struct, IAction
|
|
{
|
|
private readonly int start;
|
|
private readonly int end;
|
|
private readonly int batchSize;
|
|
private readonly TAction action;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public ActionInvoker(
|
|
int start,
|
|
int end,
|
|
int batchSize,
|
|
in TAction action)
|
|
{
|
|
this.start = start;
|
|
this.end = end;
|
|
this.batchSize = batchSize;
|
|
this.action = action;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processes the batch of actions at a specified index
|
|
/// </summary>
|
|
/// <param name="i">The index of the batch to process</param>
|
|
public void Invoke(int i)
|
|
{
|
|
int offset = i * this.batchSize;
|
|
int low = this.start + offset;
|
|
int high = low + this.batchSize;
|
|
int stop = Math.Min(high, this.end);
|
|
|
|
for (int j = low; j < stop; j++)
|
|
{
|
|
Unsafe.AsRef(this.action).Invoke(j);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A contract for actions being executed with an input index.
|
|
/// </summary>
|
|
/// <remarks>If the <see cref="Invoke"/> method is small enough, it is highly recommended to mark it with <see cref="MethodImplOptions.AggressiveInlining"/>.</remarks>
|
|
public interface IAction
|
|
{
|
|
/// <summary>
|
|
/// Executes the action associated with a specific index.
|
|
/// </summary>
|
|
/// <param name="i">The current index for the action to execute.</param>
|
|
void Invoke(int i);
|
|
}
|