Skip to content

Commit f4b1160

Browse files
committed
@Perf using type pool for more than DynamicMethod. @wip reusing DynamicMethod
1 parent 213de31 commit f4b1160

File tree

3 files changed

+67
-32
lines changed

3 files changed

+67
-32
lines changed

src/FastExpressionCompiler/FastExpressionCompiler.cs

Lines changed: 51 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@ internal static TDelegate TryCompileWithPreCreatedClosure<TDelegate>(
437437

438438
var delegateType = typeof(TDelegate) != typeof(Delegate) ? typeof(TDelegate) : lambdaExpr.Type;
439439
var @delegate = (TDelegate)(object)method.CreateDelegate(delegateType, new ArrayClosure(closureInfo.Constants.Items));
440-
ReturnClosureTypeToParamTypesToPool(closurePlusParamTypes);
440+
FreeClosureTypeToParamTypesToPool(closurePlusParamTypes);
441441
return @delegate;
442442
}
443443

@@ -468,7 +468,7 @@ public static TDelegate TryCompileWithoutClosure<TDelegate>(this LambdaExpressio
468468

469469
var delegateType = typeof(TDelegate) != typeof(Delegate) ? typeof(TDelegate) : lambdaExpr.Type;
470470
var @delegate = (TDelegate)(object)method.CreateDelegate(delegateType, EmptyArrayClosure);
471-
ReturnClosureTypeToParamTypesToPool(closurePlusParamTypes);
471+
FreeClosureTypeToParamTypesToPool(closurePlusParamTypes);
472472
return @delegate;
473473
}
474474

@@ -545,43 +545,70 @@ internal static object TryCompileBoundToFirstClosureParam(Type delegateType, Exp
545545
return null;
546546
il.Demit(OpCodes.Ret);
547547

548-
ReturnClosureTypeToParamTypesToPool(closurePlusParamTypes);
548+
FreeClosureTypeToParamTypesToPool(closurePlusParamTypes);
549549

550550
return method.CreateDelegate(delegateType, closure);
551551
}
552552

553553
private static readonly Type[] _closureAsASingleParamType = { typeof(ArrayClosure) };
554-
private static readonly Type[][] _closureTypePlusParamTypesPool = new Type[8][]; // todo: @perf @mem could we use this for other Type arrays?
554+
private static readonly Type[][] _paramTypesPoolWithElem0OfLength1 = new Type[8][]; // todo: @perf @mem could we use this for other Type arrays?
555555

556556
#if LIGHT_EXPRESSION
557-
private static Type[] RentOrNewClosureTypeToParamTypes(IParameterProvider paramExprs)
557+
internal static Type[] RentOrNewClosureTypeToParamTypes(IParameterProvider paramExprs)
558558
{
559559
var count = paramExprs.ParameterCount;
560560
#else
561-
private static Type[] RentOrNewClosureTypeToParamTypes(IReadOnlyList<PE> paramExprs)
561+
internal static Type[] RentOrNewClosureTypeToParamTypes(IReadOnlyList<PE> paramExprs)
562562
{
563563
var count = paramExprs.Count;
564564
#endif
565565
if (count == 0)
566566
return _closureAsASingleParamType;
567567

568-
var pooled = count < 8 ? Interlocked.Exchange(ref _closureTypePlusParamTypesPool[count], null) ?? new Type[count + 1] : new Type[count + 1];
569-
pooled[0] = typeof(ArrayClosure);
568+
var pooledOrNew = count < 8 ? Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[count], null) ?? new Type[count + 1] : new Type[count + 1];
569+
pooledOrNew[0] = typeof(ArrayClosure);
570570
for (var i = 0; i < count; i++)
571571
{
572572
var paramExpr = paramExprs.GetParameter(i); // todo: @perf can we avoid calling virtual GetParameter() and maybe use intrinsic with NoByRef?
573-
pooled[i + 1] = !paramExpr.IsByRef ? paramExpr.Type : paramExpr.Type.MakeByRefType();
573+
pooledOrNew[i + 1] = !paramExpr.IsByRef ? paramExpr.Type : paramExpr.Type.MakeByRefType();
574574
}
575575

576-
return pooled;
576+
return pooledOrNew;
577577
}
578578

579579
[MethodImpl((MethodImplOptions)256)]
580-
private static void ReturnClosureTypeToParamTypesToPool(Type[] closurePlusParamTypes)
580+
internal static Type[] RentOrNewClosureTypeToParamTypes(Type p1, Type p2)
581+
{
582+
var pooledOrNew = Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[2], null) ?? new Type[3];
583+
pooledOrNew[0] = typeof(ArrayClosure);
584+
pooledOrNew[1] = p1;
585+
pooledOrNew[2] = p2;
586+
return pooledOrNew;
587+
}
588+
589+
[MethodImpl((MethodImplOptions)256)]
590+
internal static Type[] RentParamTypes(Type p0, Type p1)
591+
{
592+
var pooledOrNew = Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[1], null) ?? new Type[2];
593+
pooledOrNew[0] = p0;
594+
pooledOrNew[1] = p1;
595+
return pooledOrNew;
596+
}
597+
598+
[MethodImpl((MethodImplOptions)256)]
599+
internal static void FreeClosureTypeToParamTypesToPool(Type[] closurePlusParamTypes)
581600
{
582601
var paramCountOnly = closurePlusParamTypes.Length - 1;
583602
if (paramCountOnly != 0 & paramCountOnly < 8)
584-
Interlocked.Exchange(ref _closureTypePlusParamTypesPool[paramCountOnly], closurePlusParamTypes); // todo: @perf we don't need the Interlocked here
603+
Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[paramCountOnly], closurePlusParamTypes); // todo: @perf we don't need the Interlocked here
604+
}
605+
606+
[MethodImpl((MethodImplOptions)256)]
607+
internal static void FreeParamTypes(Type[] paramTypes)
608+
{
609+
var paramCount = paramTypes.Length;
610+
if (paramCount != 0 & paramCount < 8)
611+
Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[paramCount - 1], paramTypes); // todo: @perf we don't need the Interlocked here
585612
}
586613

587614
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
@@ -1735,7 +1762,7 @@ private static bool TryCompileNestedLambda(ref ClosureInfo nestedClosureInfo, Ne
17351762
{
17361763
var paramTypes = RentOrNewClosureTypeToParamTypes(nestedLambdaParamExprs);
17371764
nestedLambdaInfo.Lambda = CompileNoArgsNew(newNoArgs.Constructor, nestedLambdaExpr.Type, paramTypes, nestedReturnType);
1738-
ReturnClosureTypeToParamTypesToPool(paramTypes);
1765+
FreeClosureTypeToParamTypesToPool(paramTypes);
17391766
return true;
17401767
}
17411768
#else
@@ -1780,7 +1807,7 @@ private static bool TryCompileNestedLambda(ref ClosureInfo nestedClosureInfo, Ne
17801807
: nestedConstsAndLambdas == null ? new NestedLambdaForNonPassedParams(nestedLambda)
17811808
: new NestedLambdaForNonPassedParamsWithConstants(nestedLambda, nestedConstsAndLambdas);
17821809

1783-
ReturnClosureTypeToParamTypesToPool(closurePlusParamTypes);
1810+
FreeClosureTypeToParamTypesToPool(closurePlusParamTypes);
17841811
return true;
17851812
}
17861813

@@ -8325,7 +8352,7 @@ public virtual LocalBuilder DeclareLocal(Type localType, bool pinned)
83258352
}
83268353
*/
83278354

8328-
private static readonly Func<ILGenerator, Type, int> _getNextLocalVarIndex;
8355+
internal static readonly Func<ILGenerator, Type, int> _getNextLocalVarIndex;
83298356

83308357
internal static int PostInc(ref int i) => i++;
83318358

@@ -8345,26 +8372,18 @@ static ILGeneratorHacks()
83458372
return;
83468373

83478374
// looking for the `SignatureHelper.AddArgument(Type argument, bool pinned)`
8348-
MethodInfo addArgumentMethod = null;
8349-
foreach (var m in typeof(SignatureHelper).GetTypeInfo().GetDeclaredMethods("AddArgument"))
8350-
{
8351-
var ps = m.GetParameters();
8352-
if (ps.Length == 2 && ps[0].ParameterType == typeof(Type) && ps[1].ParameterType == typeof(bool))
8353-
{
8354-
addArgumentMethod = m;
8355-
break;
8356-
}
8357-
}
8358-
8375+
var typeAndBoolParamTypes = ExpressionCompiler.RentParamTypes(typeof(Type), typeof(bool));
8376+
var addArgumentMethod = typeof(SignatureHelper).GetMethod("AddArgument", typeAndBoolParamTypes);
83598377
if (addArgumentMethod == null)
83608378
return;
8379+
ExpressionCompiler.FreeParamTypes(typeAndBoolParamTypes);
83618380

83628381
// our own helper - always available
8363-
var postIncMethod = typeof(ILGeneratorHacks).GetTypeInfo().GetDeclaredMethod(nameof(PostInc));
8382+
var postIncMethod = typeof(ILGeneratorHacks).GetMethod(nameof(PostInc), BindingFlags.Static | BindingFlags.NonPublic);
8383+
Debug.Assert(postIncMethod != null, "PostInc method not found!");
83648384

8365-
var efficientMethod = new DynamicMethod(string.Empty,
8366-
typeof(int), new[] { typeof(ExpressionCompiler.ArrayClosure), typeof(ILGenerator), typeof(Type) },
8367-
typeof(ExpressionCompiler.ArrayClosure), skipVisibility: true);
8385+
var paramTypes = ExpressionCompiler.RentOrNewClosureTypeToParamTypes(typeof(ILGenerator), typeof(Type));
8386+
var efficientMethod = new DynamicMethod(string.Empty, typeof(int), paramTypes, typeof(ExpressionCompiler.ArrayClosure), true);
83688387
var il = efficientMethod.GetILGenerator();
83698388

83708389
// emitting `il.m_localSignature.AddArgument(type);`
@@ -8384,6 +8403,8 @@ static ILGeneratorHacks()
83848403
_getNextLocalVarIndex = (Func<ILGenerator, Type, int>)efficientMethod.CreateDelegate(
83858404
typeof(Func<ILGenerator, Type, int>), ExpressionCompiler.EmptyArrayClosure);
83868405

8406+
ExpressionCompiler.FreeClosureTypeToParamTypesToPool(paramTypes);
8407+
83878408
// todo: @perf do batch Emit by manually calling `EnsureCapacity` once then `InternalEmit` multiple times
83888409
// todo: @perf Replace the `Emit(opcode, int)` with the more specialized `Emit(opcode)`, `Emit(opcode, byte)` or `Emit(opcode, short)`
83898410
// avoiding internal check for Ldc_I4, Ldarg, Ldarga, Starg then call `PutInteger4` only if needed see https://source.dot.net/#System.Private.CoreLib/src/System/Reflection/Emit/ILGenerator.cs,690f350859394132

test/FastExpressionCompiler.IssueTests/EmitHacksTest.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,13 @@
1111
namespace FastExpressionCompiler.IssueTests
1212
{
1313

14-
public class EmitHacksTest : ITest
14+
public class EmitHacksTest : ITest, ITestX
1515
{
16+
public void Run(TestRun t)
17+
{
18+
TryToReuseTheDynamicMethod(t);
19+
}
20+
1621
public int Run()
1722
{
1823
DynamicMethod_Emit_Hack();
@@ -21,7 +26,11 @@ public int Run()
2126
return 3;
2227
}
2328

24-
29+
void TryToReuseTheDynamicMethod(TestContext t)
30+
{
31+
t.Fail("TryToReuseTheDynamicMethod is not implemented yet");
32+
}
33+
2534
public void DynamicMethod_Emit_Hack()
2635
{
2736
var f = Get_DynamicMethod_Emit_Hack();

test/FastExpressionCompiler.TestsRunner/Program.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ public class Program
1111
{
1212
public static void Main()
1313
{
14+
#if NET8_0_OR_GREATER && !LIGHT_EXPRESSION
15+
var ts = new TestRun();
16+
ts.Run(new EmitHacksTest());
17+
#endif
18+
1419
var t = new LightExpression.TestRun();
1520

1621
t.Run(new LightExpression.IssueTests.Issue468_Optimize_the_delegate_access_to_the_Closure_object_for_the_modern_NET());

0 commit comments

Comments
 (0)