Skip to content

Commit eb0ceb1

Browse files
committed
improve perf #472 via less boxing
1 parent 8197a45 commit eb0ceb1

File tree

3 files changed

+67
-23
lines changed

3 files changed

+67
-23
lines changed

src/FastExpressionCompiler/FastExpressionCompiler.cs

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6397,6 +6397,16 @@ public static class Interpreter
63976397
/// <summary>Always returns false</summary>
63986398
public static readonly Func<bool> FalseFunc = static () => false;
63996399

6400+
/// <summary>Single instance of true object</summary>
6401+
public static readonly object TrueObject = true;
6402+
/// <summary>Single instance of false object</summary>
6403+
public static readonly object FalseObject = false;
6404+
6405+
private static T UnreachableCase<T>()
6406+
{
6407+
throw new InvalidCastException("Unreachable switch case reached");
6408+
}
6409+
64006410
/// <summary>Operation accepting bool inputs and producing bool output</summary>
64016411
[MethodImpl(MethodImplOptions.AggressiveInlining)]
64026412
public static bool IsLogical(ExpressionType nodeType) =>
@@ -6553,7 +6563,7 @@ public static bool TryEvalPureArithmeticAndLogic(out object result, Expression e
65536563
public static bool TryEvalPrimitive(out object result, Expression expr)
65546564
{
65556565
Debug.Assert(expr.Type.IsPrimitive);
6556-
result = false;
6566+
result = null;
65576567

65586568
var nodeType = expr.NodeType;
65596569
if (nodeType == ExpressionType.Constant)
@@ -6566,6 +6576,8 @@ public static bool TryEvalPrimitive(out object result, Expression expr)
65666576
return true;
65676577
}
65686578

6579+
// todo: @wip handle the DefaultExpression
6580+
65696581
if (nodeType == ExpressionType.Convert)
65706582
{
65716583
var unaryExpr = (UnaryExpression)expr;
@@ -6584,7 +6596,7 @@ public static bool TryEvalPrimitive(out object result, Expression expr)
65846596
var unaryExpr = (UnaryExpression)expr;
65856597
if (!TryEvalPrimitive(out var boolVal, unaryExpr.Operand))
65866598
return false;
6587-
result = !(bool)boolVal;
6599+
result = boolVal == TrueObject ? FalseObject : TrueObject;
65886600
return true;
65896601
}
65906602

@@ -6595,42 +6607,45 @@ public static bool TryEvalPrimitive(out object result, Expression expr)
65956607
return false;
65966608

65976609
// Short circuit the evalution, because this is an actual logic of these logical operations
6598-
if ((bool)leftVal)
6599-
return nodeType == ExpressionType.OrElse
6600-
|| TryEvalPrimitive(out result, binaryExpr.Right);
6601-
// left is false
6602-
if (nodeType == ExpressionType.AndAlso)
6603-
result = leftVal; // return the false result
6604-
// otherwise for || evaluate the right result
6610+
if (leftVal == TrueObject & nodeType == ExpressionType.OrElse ||
6611+
leftVal == FalseObject & nodeType == ExpressionType.AndAlso)
6612+
{
6613+
result = leftVal;
6614+
return true;
6615+
}
66056616
return TryEvalPrimitive(out result, binaryExpr.Right);
66066617
}
66076618

66086619
if (IsComparison(nodeType))
66096620
{
66106621
var binaryExpr = (BinaryExpression)expr;
6611-
if (!TryEvalPrimitive(out var left, binaryExpr.Left) ||
6612-
!TryEvalPrimitive(out var right, binaryExpr.Right))
6622+
if (!TryEvalPrimitive(out var leftVal, binaryExpr.Left) ||
6623+
!TryEvalPrimitive(out var rightVal, binaryExpr.Right))
66136624
return false;
66146625

6615-
if (nodeType == ExpressionType.Equal)
6616-
result = left.Equals(right);
6617-
else if (nodeType == ExpressionType.NotEqual)
6618-
result = !left.Equals(right);
6626+
if (nodeType == ExpressionType.Equal | nodeType == ExpressionType.NotEqual)
6627+
{
6628+
var boolVal = leftVal.Equals(rightVal);
6629+
result = nodeType == ExpressionType.Equal
6630+
? (boolVal ? TrueObject : FalseObject)
6631+
: (boolVal ? FalseObject : TrueObject);
6632+
}
66196633
else
66206634
{
66216635
// Assuming that the both sides are of the same type, we can use only the left one for comparison
6622-
var cmp = left as IComparable;
6636+
var cmp = leftVal as IComparable;
66236637
if (cmp == null)
66246638
return false;
6625-
var res = cmp.CompareTo(right);
6626-
result = nodeType switch
6639+
var res = cmp.CompareTo(rightVal);
6640+
var boolVal = nodeType switch
66276641
{
66286642
ExpressionType.GreaterThan => res > 0,
66296643
ExpressionType.GreaterThanOrEqual => res >= 0,
66306644
ExpressionType.LessThan => res < 0,
66316645
ExpressionType.LessThanOrEqual => res <= 0,
6632-
_ => null,
6646+
_ => UnreachableCase<bool>(),
66336647
};
6648+
result = boolVal ? TrueObject : FalseObject;
66346649
}
66356650
return true;
66366651
}
@@ -6655,14 +6670,16 @@ public static bool TryEvalPrimitive(out object result, Expression expr)
66556670
return result != null;
66566671
}
66576672

6658-
result = false;
6673+
result = null;
66596674
return false;
66606675
}
66616676
}
66626677
}
66636678

6664-
// Helpers targeting the performance. Extensions method names may be a bit funny (non standard),
6665-
// in order to prevent conflicts with YOUR helpers with standard names
6679+
/// <summary>
6680+
/// Helpers targeting the performance. Extensions method names may be a bit funny (non standard),
6681+
/// in order to prevent conflicts with YOUR helpers with standard names
6682+
/// </summary>
66666683
public static class Tools
66676684
{
66686685
public static Expression AsExpr(this object obj) => obj as Expression ?? Constant(obj);

test/FastExpressionCompiler.Benchmarks/Issue468_Compile_vs_FastCompile.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,29 @@ public object CompiledFast_WithEvalFlag()
194194
return _expr.CompileFast(flags: CompilerFlags.TryEvalPureArithmeticAndLogic);
195195
}
196196
}
197+
198+
[MemoryDiagnoser, RankColumn]
199+
// [SimpleJob(RuntimeMoniker.Net90)]
200+
// [SimpleJob(RuntimeMoniker.Net80)]
201+
public class Issue468_Eval_Optimization
202+
{
203+
Expression<Func<bool>> _expr;
204+
205+
[GlobalSetup]
206+
public void Setup()
207+
{
208+
_expr = IssueTests.Issue468_Optimize_the_delegate_access_to_the_Closure_object_for_the_modern_NET.CreateExpression();
209+
}
210+
211+
// [Benchmark(Baseline = true)]
212+
// public object Baseline()
213+
// {
214+
// return ExpressionCompiler.Interpreter.TryEvalPrimitive_OLD(out var result, _expr) ? result : null;
215+
// }
216+
217+
[Benchmark]
218+
public object Optimized()
219+
{
220+
return ExpressionCompiler.Interpreter.TryEvalPrimitive(out var result, _expr) ? result : null;
221+
}
222+
}

test/FastExpressionCompiler.Benchmarks/Program.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ public static void Main()
2525

2626
//--------------------------------------------
2727

28-
BenchmarkRunner.Run<Issue468_Compile_vs_FastCompile>();
28+
// BenchmarkRunner.Run<Issue468_Compile_vs_FastCompile>();
29+
BenchmarkRunner.Run<Issue468_Eval_Optimization>();
2930
// BenchmarkRunner.Run<Issue468_InvokeCompiled_vs_InvokeCompiledFast>();
3031

3132
// BenchmarkRunner.Run<AccessByRef_vs_ByIGetRefStructImpl>();

0 commit comments

Comments
 (0)