Last active
October 1, 2023 21:57
-
-
Save aalmada/dce5b6f8f242932392ff14d00f5360d0 to your computer and use it in GitHub Desktop.
Implementation of the ForEach method for all types of collections using value delegated and other performance optimizations.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Numerics; | |
using System.Runtime.CompilerServices; | |
using System.Runtime.InteropServices; | |
using static System.Runtime.InteropServices.JavaScript.JSType; | |
namespace NetFabric; | |
public interface IAction<in T> | |
{ | |
void Invoke(T arg); | |
} | |
public interface IVectorAction<T> : IAction<T> | |
where T : struct | |
{ | |
void Invoke(in Vector<T> arg); | |
} | |
public static partial class Extensions | |
{ | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action) | |
{ | |
var actionWrapper = new ActionWrapper<T>(action); | |
source.ForEach(ref actionWrapper); | |
} | |
public static void ForEach<T, TAction>(this IEnumerable<T> source, ref TAction action) | |
where TAction : struct, IAction<T> | |
{ | |
if (source.GetType() == typeof(T[])) | |
{ | |
ForEach(Unsafe.As<T[]>(source), ref action); | |
} | |
else if (source.GetType() == typeof(List<T>)) | |
{ | |
ForEach(Unsafe.As<List<T>>(source), ref action); | |
} | |
else | |
{ | |
foreach (var item in source) | |
action.Invoke(item); | |
} | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static void ForEachEx<T>(this List<T> source, Action<T> action) | |
{ | |
var actionWrapper = new ActionWrapper<T>(action); | |
source.ForEach(ref actionWrapper); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static void ForEach<T, TAction>(this List<T> source, ref TAction action) | |
where TAction : struct, IAction<T> | |
=> ForEach(CollectionsMarshal.AsSpan(Unsafe.As<List<T>>(source)), ref action); | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static void ForEach<T>(this T[] source, Action<T> action) | |
{ | |
var actionWrapper = new ActionWrapper<T>(action); | |
source.ForEach(ref actionWrapper); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static void ForEach<T, TAction>(this T[] source, ref TAction action) | |
where TAction : struct, IAction<T> | |
=> ForEach(source.AsSpan(), ref action); | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static void ForEach<T>(this Span<T> source, Action<T> action) | |
{ | |
var actionWrapper = new ActionWrapper<T>(action); | |
source.ForEach(ref actionWrapper); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static void ForEach<T, TAction>(this Span<T> source, ref TAction action) | |
where TAction : struct, IAction<T> | |
=> ForEach((ReadOnlySpan<T>)source, ref action); | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static void ForEach<T>(this ReadOnlySpan<T> source, Action<T> action) | |
{ | |
var actionWrapper = new ActionWrapper<T>(action); | |
source.ForEach(ref actionWrapper); | |
} | |
public static void ForEach<T, TAction>(this ReadOnlySpan<T> source, ref TAction action) | |
where TAction : struct, IAction<T> | |
{ | |
foreach (ref readonly var item in source) | |
action.Invoke(item); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static void ForEachVector<T, TAction>(this List<T> source, ref TAction action) | |
where T : struct | |
where TAction : struct, IVectorAction<T> | |
=> ForEach(CollectionsMarshal.AsSpan(source), ref action); | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static void ForEachVector<T, TAction>(this T[] source, ref TAction action) | |
where T : struct | |
where TAction : struct, IVectorAction<T> | |
=> ForEach(source.AsSpan(), ref action); | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static void ForEachVector<T, TAction>(this Span<T> source, ref TAction action) | |
where T : struct | |
where TAction : struct, IVectorAction<T> | |
=> ForEach((ReadOnlySpan<T>)source, ref action); | |
public static void ForEachVector<T, TAction>(this ReadOnlySpan<T> source, ref TAction action) | |
where T : struct | |
where TAction : struct, IVectorAction<T> | |
{ | |
if (Vector.IsHardwareAccelerated && Vector<T>.IsSupported && source.Length > Vector<T>.Count) | |
{ | |
var vectors = MemoryMarshal.Cast<T, Vector<T>>(source); | |
foreach (ref readonly var vector in vectors) | |
action.Invoke(in vector); | |
var remainder = source.Length % Vector<T>.Count; | |
source = source.Slice(remainder, source.Length - remainder); | |
} | |
foreach (ref readonly var item in source) | |
{ | |
action.Invoke(item); | |
} | |
} | |
readonly struct ActionWrapper<T> | |
: IAction<T> | |
{ | |
readonly Action<T> action; | |
public ActionWrapper(Action<T> action) | |
=> this.action = action ?? throw new ArgumentNullException(nameof(action)); | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public void Invoke(T arg) | |
=> action(arg); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Numerics; | |
using System.Runtime.CompilerServices; | |
using System.Runtime.InteropServices; | |
using BenchmarkDotNet.Attributes; | |
using BenchmarkDotNet.Configs; | |
using NetFabric; | |
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] | |
[CategoriesColumn] | |
public class ForEachBenchmarks | |
{ | |
IEnumerable<int>? enumerable; | |
List<int>? list; | |
IEnumerable<int>? listAsEnumerable; | |
int[]? array; | |
IEnumerable<int>? arrayAsEnumerable; | |
[Params(10, 1_000)] | |
public int Count { get; set; } | |
static IEnumerable<int> GetEnumerable(int count) | |
{ | |
var random = new Random(42); | |
for (var item = 0; item < count; item++) | |
yield return random.Next(count); | |
} | |
[GlobalSetup] | |
public void GlobalSetup() | |
{ | |
enumerable = GetEnumerable(Count); | |
list = enumerable.ToList(); | |
listAsEnumerable = list; | |
array = enumerable.ToArray(); | |
arrayAsEnumerable = array; | |
} | |
[BenchmarkCategory("Enumerable")] | |
[Benchmark(Baseline = true)] | |
public int Enumerable_foreach() | |
{ | |
var sum = 0; | |
foreach (var item in enumerable!) | |
sum += item; | |
return sum; | |
} | |
[BenchmarkCategory("Enumerable")] | |
[Benchmark] | |
public int Enumerable_MoreLinq() | |
{ | |
var sum = 0; | |
MoreLinq.MoreEnumerable.ForEach(enumerable!, item => sum += item); | |
return sum; | |
} | |
[BenchmarkCategory("Enumerable")] | |
[Benchmark] | |
public int Enumerable_ForEach_Action() | |
{ | |
var sum = 0; | |
enumerable!.ForEach(item => sum += item); | |
return sum; | |
} | |
[BenchmarkCategory("Enumerable")] | |
[Benchmark] | |
public int Enumerable_ForEach_ValueAction() | |
{ | |
var sum = new SumAction<int>(); | |
enumerable!.ForEach(ref sum); | |
return sum.Result; | |
} | |
[BenchmarkCategory("List")] | |
[Benchmark(Baseline = true)] | |
public int List_foreach() | |
{ | |
var sum = 0; | |
foreach (var item in list!) | |
sum += item; | |
return sum; | |
} | |
[BenchmarkCategory("List")] | |
[Benchmark] | |
public int List_foreach_AsSpan() | |
{ | |
var sum = 0; | |
foreach (var item in CollectionsMarshal.AsSpan(list!)) | |
sum += item; | |
return sum; | |
} | |
[BenchmarkCategory("List")] | |
[Benchmark] | |
public int List_ForEach() | |
{ | |
var sum = 0; | |
list!.ForEach(item => sum += item); | |
return sum; | |
} | |
[BenchmarkCategory("List")] | |
[Benchmark] | |
public int List_MoreLinq() | |
{ | |
var sum = 0; | |
MoreLinq.MoreEnumerable.ForEach(list!, item => sum += item); | |
return sum; | |
} | |
[BenchmarkCategory("List")] | |
[Benchmark] | |
public int List_ForEach_Action() | |
{ | |
var sum = 0; | |
list!.ForEachEx(item => sum += item); | |
return sum; | |
} | |
[BenchmarkCategory("List")] | |
[Benchmark] | |
public int List_ForEach_ValueAction() | |
{ | |
var sum = new SumAction<int>(); | |
list!.ForEach(ref sum); | |
return sum.Result; | |
} | |
[BenchmarkCategory("List")] | |
[Benchmark] | |
public int List_ForEach_VectorAction() | |
{ | |
var sum = new SumVectorAction<int>(); | |
list!.ForEachVector(ref sum); | |
return sum.Result; | |
} | |
[BenchmarkCategory("List")] | |
[Benchmark] | |
public int ListAsEnumerable_ForEach_Action() | |
{ | |
var sum = 0; | |
listAsEnumerable!.ForEach(item => sum += item); | |
return sum; | |
} | |
[BenchmarkCategory("List")] | |
[Benchmark] | |
public int ListAsEnumerable_ForEach_ValueAction() | |
{ | |
var sum = new SumAction<int>(); | |
listAsEnumerable!.ForEach(ref sum); | |
return sum.Result; | |
} | |
[BenchmarkCategory("Array")] | |
[Benchmark(Baseline = true)] | |
public int Array_foreach() | |
{ | |
var sum = 0; | |
foreach (var item in array!) | |
sum += item; | |
return sum; | |
} | |
[BenchmarkCategory("Array")] | |
[Benchmark] | |
public int Array_MoreLinq() | |
{ | |
var sum = 0; | |
MoreLinq.MoreEnumerable.ForEach(array!, item => sum += item); | |
return sum; | |
} | |
[BenchmarkCategory("Array")] | |
[Benchmark] | |
public int Array_ForEach_Action() | |
{ | |
var sum = 0; | |
array!.ForEach(item => sum += item); | |
return sum; | |
} | |
[BenchmarkCategory("Array")] | |
[Benchmark] | |
public int Array_ForEach_ValueAction() | |
{ | |
var sum = new SumAction<int>(); | |
array!.ForEach(ref sum); | |
return sum.Result; | |
} | |
[BenchmarkCategory("Array")] | |
[Benchmark] | |
public int Array_ForEach_VectorAction() | |
{ | |
var sum = new SumVectorAction<int>(); | |
array!.ForEachVector(ref sum); | |
return sum.Result; | |
} | |
[BenchmarkCategory("Array")] | |
[Benchmark] | |
public int ArrayAsEnumerable_ForEach_Action() | |
{ | |
var sum = 0; | |
arrayAsEnumerable!.ForEach(item => sum += item); | |
return sum; | |
} | |
[BenchmarkCategory("Array")] | |
[Benchmark] | |
public int ArrayAsEnumerable_ForEach_ValueAction() | |
{ | |
var sum = new SumAction<int>(); | |
arrayAsEnumerable!.ForEach(ref sum); | |
return sum.Result; | |
} | |
} | |
[StructLayout(LayoutKind.Auto)] | |
struct SumAction<T> | |
: IAction<T> | |
where T : struct, IAdditiveIdentity<T, T>, IAdditionOperators<T, T, T> | |
{ | |
T sum; | |
public SumAction() | |
{ | |
sum = T.AdditiveIdentity; | |
} | |
public readonly T Result | |
=> sum; | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public void Invoke(T item) | |
=> sum += item; | |
} | |
[StructLayout(LayoutKind.Auto)] | |
struct SumVectorAction<T> | |
: IVectorAction<T> | |
where T : struct, IAdditiveIdentity<T, T>, IAdditionOperators<T, T, T> | |
{ | |
T sum; | |
Vector<T> sumVector; | |
public SumVectorAction() | |
{ | |
sum = T.AdditiveIdentity; | |
sumVector = Vector<T>.Zero; | |
} | |
public readonly T Result | |
=> Vector.Sum(sumVector) + sum; | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public void Invoke(T item) | |
=> sum += item; | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public void Invoke(in Vector<T> vector) | |
=> sumVector += vector; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment