Skip to content

Instantly share code, notes, and snippets.

@aalmada
Last active October 1, 2023 21:57
Show Gist options
  • Save aalmada/dce5b6f8f242932392ff14d00f5360d0 to your computer and use it in GitHub Desktop.
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.
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);
}
}
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