Skip to content

Commit 05d5008

Browse files
Copilotdadhi
andauthored
Add switch branch-elimination benchmarks (Issue #489)
- New Issue489_Switch_BranchElimination.cs with Compile and Invoke nested classes: - Baseline: Switch(Parameter(...), ...) — runtime value, no FEC branch elimination - Eliminated: Switch(Constant(2), ...) — constant value, FEC emits only matching branch - Both registered in Program.cs (commented-out as per existing convention) Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/03a3da65-f598-4c66-90ce-0545d43edc04 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com>
1 parent d4423e8 commit 05d5008

2 files changed

Lines changed: 151 additions & 0 deletions

File tree

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
using System;
2+
using System.Linq.Expressions;
3+
using BenchmarkDotNet.Attributes;
4+
using BenchmarkDotNet.Order;
5+
6+
namespace FastExpressionCompiler.Benchmarks;
7+
8+
/// <summary>
9+
/// Benchmarks for compile-time switch branch elimination introduced in #489.
10+
///
11+
/// Two variants are compared:
12+
/// - Baseline: Switch value is a runtime parameter — FEC cannot eliminate any branches.
13+
/// - Eliminated: Switch value is a compile-time constant — FEC selects the single matching
14+
/// branch and emits only that, skipping closure collection and IL for all others.
15+
///
16+
/// Each variant has two nested classes:
17+
/// - Compile: measures how fast `Compile()` / `CompileFast()` process the expression.
18+
/// - Invoke: measures the execution speed of the resulting delegate.
19+
///
20+
/// The Invoke benchmark is the most telling: the eliminated switch emits just
21+
/// `ldstr "B" / ret` (2 IL instructions) vs the full switch table.
22+
/// </summary>
23+
public class Issue489_Switch_BranchElimination
24+
{
25+
// -----------------------------------------------------------------
26+
// Shared expression factories
27+
// -----------------------------------------------------------------
28+
29+
/// <summary>
30+
/// Baseline: the switch value is a runtime parameter — no elimination possible.
31+
/// switch (x) { case 1: "A"; case 2: "B"; case 5: "C"; default: "Z" }
32+
/// </summary>
33+
private static Expression<Func<int, string>> CreateExpr_Baseline()
34+
{
35+
var p = Expression.Parameter(typeof(int), "x");
36+
return Expression.Lambda<Func<int, string>>(
37+
Expression.Switch(p,
38+
Expression.Constant("Z"),
39+
Expression.SwitchCase(Expression.Constant("A"), Expression.Constant(1)),
40+
Expression.SwitchCase(Expression.Constant("B"), Expression.Constant(2)),
41+
Expression.SwitchCase(Expression.Constant("C"), Expression.Constant(5))),
42+
p);
43+
}
44+
45+
/// <summary>
46+
/// Branch-eliminated: the switch value is the compile-time constant <c>2</c>.
47+
/// switch (2) { case 1: "A"; case 2: "B"; case 5: "C"; default: "Z" }
48+
/// FEC reduces this to a single <c>ldstr "B" / ret</c>.
49+
/// </summary>
50+
private static Expression<Func<string>> CreateExpr_Eliminated()
51+
{
52+
return Expression.Lambda<Func<string>>(
53+
Expression.Switch(Expression.Constant(2),
54+
Expression.Constant("Z"),
55+
Expression.SwitchCase(Expression.Constant("A"), Expression.Constant(1)),
56+
Expression.SwitchCase(Expression.Constant("B"), Expression.Constant(2)),
57+
Expression.SwitchCase(Expression.Constant("C"), Expression.Constant(5))));
58+
}
59+
60+
// -----------------------------------------------------------------
61+
// Compilation benchmarks
62+
// -----------------------------------------------------------------
63+
64+
/// <summary>
65+
/// Measures how fast Compile / CompileFast process each expression variant.
66+
/// Baseline: runtime parameter switch (no FEC branch elimination).
67+
/// Eliminated: constant switch (FEC skips dead branch closure-collection and IL emission).
68+
/// </summary>
69+
[MemoryDiagnoser, RankColumn, Orderer(SummaryOrderPolicy.FastestToSlowest)]
70+
public class Compile
71+
{
72+
/*
73+
## Results placeholder — run with: dotnet run -c Release --project test/FastExpressionCompiler.Benchmarks -- --filter *Issue489*Compile*
74+
75+
| Method | Mean | Error | StdDev | Ratio | Rank | Allocated |
76+
|----------------------------- |----------:|---------:|---------:|------:|-----:|----------:|
77+
| Baseline_CompileFast | N/A | N/A | N/A | | | N/A |
78+
| Baseline_Compile | N/A | N/A | N/A | | | N/A |
79+
| Eliminated_CompileFast | N/A | N/A | N/A | | | N/A |
80+
| Eliminated_Compile | N/A | N/A | N/A | | | N/A |
81+
*/
82+
83+
private static readonly Expression<Func<int, string>> _baseline = CreateExpr_Baseline();
84+
private static readonly Expression<Func<string>> _eliminated = CreateExpr_Eliminated();
85+
86+
[Benchmark(Baseline = true)]
87+
public object Baseline_Compile() => _baseline.Compile();
88+
89+
[Benchmark]
90+
public object Baseline_CompileFast() => _baseline.CompileFast();
91+
92+
[Benchmark]
93+
public object Eliminated_Compile() => _eliminated.Compile();
94+
95+
[Benchmark]
96+
public object Eliminated_CompileFast() => _eliminated.CompileFast();
97+
}
98+
99+
// -----------------------------------------------------------------
100+
// Invocation benchmarks
101+
// -----------------------------------------------------------------
102+
103+
/// <summary>
104+
/// Measures invocation speed of the compiled delegates.
105+
/// The eliminated FEC delegate emits only 2 IL instructions (ldstr + ret),
106+
/// while the system-compiled one runs a full switch dispatch at runtime.
107+
/// </summary>
108+
[MemoryDiagnoser, RankColumn, Orderer(SummaryOrderPolicy.FastestToSlowest)]
109+
public class Invoke
110+
{
111+
/*
112+
## Results placeholder — run with: dotnet run -c Release --project test/FastExpressionCompiler.Benchmarks -- --filter *Issue489*Invoke*
113+
114+
| Method | Mean | Error | StdDev | Ratio | Rank | Allocated |
115+
|-------------------------------- |-----:|------:|-------:|------:|-----:|----------:|
116+
| Baseline_Compiled | N/A | N/A | N/A | | | N/A |
117+
| Baseline_CompiledFast | N/A | N/A | N/A | | | N/A |
118+
| Eliminated_Compiled | N/A | N/A | N/A | | | N/A |
119+
| Eliminated_CompiledFast | N/A | N/A | N/A | | | N/A |
120+
*/
121+
122+
private Func<int, string> _baselineCompiled;
123+
private Func<int, string> _baselineCompiledFast;
124+
private Func<string> _eliminatedCompiled;
125+
private Func<string> _eliminatedCompiledFast;
126+
127+
[GlobalSetup]
128+
public void Setup()
129+
{
130+
_baselineCompiled = CreateExpr_Baseline().Compile();
131+
_baselineCompiledFast = CreateExpr_Baseline().CompileFast();
132+
_eliminatedCompiled = CreateExpr_Eliminated().Compile();
133+
_eliminatedCompiledFast = CreateExpr_Eliminated().CompileFast();
134+
}
135+
136+
[Benchmark(Baseline = true)]
137+
public string Baseline_Compiled() => _baselineCompiled(2);
138+
139+
[Benchmark]
140+
public string Baseline_CompiledFast() => _baselineCompiledFast(2);
141+
142+
[Benchmark]
143+
public string Eliminated_Compiled() => _eliminatedCompiled();
144+
145+
[Benchmark]
146+
public string Eliminated_CompiledFast() => _eliminatedCompiledFast();
147+
}
148+
}

test/FastExpressionCompiler.Benchmarks/Program.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ public static void Main()
5050
//BenchmarkRunner.Run<ClosureConstantsBenchmark.Compilation>();
5151
//BenchmarkRunner.Run<ClosureConstantsBenchmark.Invocation>();
5252

53+
// BenchmarkRunner.Run<Issue489_Switch_BranchElimination.Compile>();
54+
// BenchmarkRunner.Run<Issue489_Switch_BranchElimination.Invoke>();
55+
5356
BenchmarkRunner.Run<StackSearch>();
5457
// BenchmarkRunner.Run<SmallList_Switch_vs_AsSpan_ByRef_Add>();
5558
// BenchmarkRunner.Run<SmallList_Switch_vs_AsSpan_ByRef_Access>();

0 commit comments

Comments
 (0)