// 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); } } } } }