Created
September 19, 2022 17:51
-
-
Save george-polevoy/91354e6c16b8bf0ee7cdd3eb8b260f54 to your computer and use it in GitHub Desktop.
compiled delegate cache
This file contains 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.Collections.Concurrent; | |
using System.Linq.Expressions; | |
using System.Runtime.CompilerServices; | |
namespace DelegateIdentifier; | |
public class Tests | |
{ | |
private const int n = 3; | |
private const int nIter = 10_000_000; | |
[Test] | |
public void TestFullMethod() | |
{ | |
var list = Enumerable.Range(0, n).ToList(); | |
var s = 0; | |
object prev; | |
var func = ExpressionCache.Get(static (List<int> _) => default(int), | |
static () => static l => l.SelectMany(i => Enumerable.Range(0, i).Select(j => j * 2)).Sum()); | |
for (var i = 0; i < nIter; i++) | |
{ | |
s = func(list); | |
} | |
} | |
[Test] | |
public void TestFunc_SelectMany() | |
{ | |
int s = 0; | |
var list = Enumerable.Range(0, n).ToList(); | |
Func<List<int>, int> expression = static l => l.SelectMany(i => Enumerable.Range(0, i).Select(j => j * 2)).Sum(); | |
var func = expression; | |
for (var i = 0; i < nIter; i++) | |
{ | |
s = func(list); | |
} | |
} | |
[Test] | |
public void TestCompiled_SelectMany() | |
{ | |
int s = 0; | |
var list = Enumerable.Range(0, n).ToList(); | |
Expression<Func<List<int>, int>> expression = static l => l.SelectMany(i => Enumerable.Range(0, i).Select(j => j * 2)).Sum(); | |
var func = expression.Compile(); | |
for (var i = 0; i < nIter; i++) | |
{ | |
s = func(list); | |
} | |
} | |
[Test] | |
public void TestRaw_SelectMany() | |
{ | |
var list = Enumerable.Range(0, n).ToList(); | |
for (var iter = 0; iter < nIter; iter++) | |
{ | |
var s = list.SelectMany(i => Enumerable.Range(0, i).Select(j => j * 2)).Sum(); | |
} | |
} | |
[Test] | |
public void TestForeach() | |
{ | |
var list = Enumerable.Range(0, n).ToList(); | |
for (var iter = 0; iter < nIter; iter++) | |
{ | |
var s = 0; | |
foreach (var i in list) | |
{ | |
for (var j = 0; j < i; j++) | |
{ | |
s += j * 2; | |
} | |
} | |
} | |
} | |
[Test] | |
public void TestForeachWithDelegate() | |
{ | |
var list = Enumerable.Range(0, n).ToList(); | |
Func<int> del = () => | |
{ | |
var s = 0; | |
foreach (var i in list) | |
{ | |
for (var j = 0; j < i; j++) | |
{ | |
s += j * 2; | |
} | |
} | |
return s; | |
}; | |
var d = new ConcurrentDictionary<IntPtr, object>(); | |
for (var i = 0; i < nIter; i++) | |
{ | |
var cachedDel = d.GetOrAdd((IntPtr)0, (ptr, i) => del, del); | |
var val = ((Func<int>)cachedDel).Invoke(); | |
} | |
} | |
} | |
public static class ExpressionCache | |
{ | |
private static readonly ConcurrentDictionary<IntPtr, object?> DelegateCache = new(); | |
[MethodImpl(MethodImplOptions.AggressiveOptimization)] | |
private static IntPtr GetMethodId(Delegate del) | |
{ | |
return del.Method.MethodHandle.Value; | |
} | |
[MethodImpl(MethodImplOptions.AggressiveOptimization)] | |
public static Func<TArg, TResult> Get<TArg, TResult>(Func<TArg, TResult> _, Func<Expression<Func<TArg, TResult>>> getter) | |
{ | |
var del = DelegateCache.GetOrAdd(GetMethodId(getter), static (_, arg) => | |
{ | |
var compiled = arg().Compile(); | |
return compiled; | |
}, getter); | |
return (Func<TArg, TResult>)del!; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment