Skip to content

Commit d953524

Browse files
authored
Merge pull request #477 from dadhi/wip-issue475
Feat: issue475
2 parents 512249e + 6c4d322 commit d953524

File tree

50 files changed

+2837
-2188
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+2837
-2188
lines changed

README.md

Lines changed: 123 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ In addition, the memory consumption taken by the compilation will be much smalle
5858
**Updated to .NET 9.0**
5959

6060
```ini
61-
BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4391/23H2/2023Update/SunValley3)
61+
BenchmarkDotNet v0.15.0, Windows 11 (10.0.26100.4061/24H2/2024Update/HudsonValley)
6262
Intel Core i9-8950HK CPU 2.90GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
63-
.NET SDK 9.0.100
64-
[Host] : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX2
65-
DefaultJob : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX2
63+
.NET SDK 9.0.203
64+
[Host] : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2
65+
DefaultJob : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2
6666
```
6767

6868
### Hoisted expression with the constructor and two arguments in closure
@@ -75,18 +75,18 @@ Expression<Func<X>> e = () => new X(a, b);
7575

7676
Compiling expression:
7777

78-
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
79-
| ----------- | ---------: | --------: | --------: | ----: | ------: | -----: | -----: | --------: | ----------: |
80-
| Compile | 151.570 us | 3.0196 us | 6.7538 us | 44.27 | 2.13 | 0.7324 | - | 4.49 KB | 2.92 |
81-
| CompileFast | 3.425 us | 0.0676 us | 0.0664 us | 1.00 | 0.03 | 0.2441 | 0.2365 | 1.54 KB | 1.00 |
78+
| Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio |
79+
| ----------- | ---------: | --------: | --------: | ----: | ------: | ---: | -----: | -----: | --------: | ----------: |
80+
| CompileFast | 3.183 us | 0.0459 us | 0.0407 us | 1.00 | 0.02 | 1 | 0.1984 | 0.1945 | 1.23 KB | 1.00 |
81+
| Compile | 147.312 us | 1.9291 us | 1.8946 us | 46.28 | 0.81 | 2 | 0.4883 | 0.2441 | 4.48 KB | 3.65 |
8282

8383
Invoking the compiled delegate (comparing to the direct constructor call):
8484

85-
| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
86-
| --------------------- | -------: | --------: | --------: | -------: | ----: | ------: | -----: | --------: | ----------: |
87-
| DirectConstructorCall | 6.920 ns | 0.2007 ns | 0.3462 ns | 7.051 ns | 0.86 | 0.06 | 0.0051 | 32 B | 1.00 |
88-
| CompiledLambda | 8.095 ns | 0.2195 ns | 0.5216 ns | 7.845 ns | 1.01 | 0.08 | 0.0051 | 32 B | 1.00 |
89-
| FastCompiledLambda | 8.066 ns | 0.2206 ns | 0.3234 ns | 8.156 ns | 1.00 | 0.06 | 0.0051 | 32 B | 1.00 |
85+
| Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Allocated | Alloc Ratio |
86+
| --------------------- | -------: | --------: | --------: | ----: | ------: | ---: | -----: | --------: | ----------: |
87+
| DirectConstructorCall | 6.055 ns | 0.0632 ns | 0.0560 ns | 1.00 | 0.01 | 1 | 0.0051 | 32 B | 1.00 |
88+
| CompiledLambda | 7.853 ns | 0.2013 ns | 0.1681 ns | 1.30 | 0.03 | 2 | 0.0051 | 32 B | 1.00 |
89+
| FastCompiledLambda | 7.962 ns | 0.2186 ns | 0.4052 ns | 1.31 | 0.07 | 2 | 0.0051 | 32 B | 1.00 |
9090

9191

9292
### Hoisted expression with the static method and two nested lambdas and two arguments in closure
@@ -99,19 +99,18 @@ Expression<Func<X>> getXExpr = () => CreateX((aa, bb) => new X(aa, bb), new Lazy
9999

100100
Compiling expression:
101101

102-
| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
103-
| ----------- | --------: | -------: | --------: | --------: | ----: | ------: | -----: | -----: | --------: | ----------: |
104-
| Compile | 421.09 us | 8.382 us | 18.221 us | 413.02 us | 36.29 | 2.09 | 1.9531 | 0.9766 | 12.04 KB | 2.61 |
105-
| CompileFast | 11.62 us | 0.230 us | 0.464 us | 11.42 us | 1.00 | 0.06 | 0.7324 | 0.7019 | 4.62 KB | 1.00 |
106-
102+
| Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio |
103+
| ----------- | --------: | -------: | -------: | ----: | ------: | ---: | -----: | -----: | --------: | ----------: |
104+
| CompileFast | 11.12 us | 0.189 us | 0.158 us | 1.00 | 0.02 | 1 | 0.6104 | 0.5798 | 3.77 KB | 1.00 |
105+
| Compile | 415.09 us | 4.277 us | 3.571 us | 37.34 | 0.60 | 2 | 1.9531 | 1.4648 | 12.04 KB | 3.19 |
107106

108107
Invoking compiled delegate comparing to direct method call:
109108

110-
| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
111-
| ------------------- | ----------: | --------: | --------: | ----------: | ----: | ------: | -----: | --------: | ----------: |
112-
| DirectMethodCall | 43.45 ns | 0.922 ns | 1.905 ns | 44.13 ns | 1.09 | 0.08 | 0.0268 | 168 B | 1.62 |
113-
| Invoke_Compiled | 1,181.25 ns | 23.664 ns | 56.240 ns | 1,161.87 ns | 29.66 | 2.24 | 0.0420 | 264 B | 2.54 |
114-
| Invoke_CompiledFast | 39.96 ns | 0.856 ns | 2.442 ns | 38.96 ns | 1.00 | 0.08 | 0.0166 | 104 B | 1.00 |
109+
| Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Allocated | Alloc Ratio |
110+
| ------------------- | ----------: | --------: | --------: | ----: | ------: | ---: | -----: | --------: | ----------: |
111+
| DirectMethodCall | 40.29 ns | 0.549 ns | 0.487 ns | 1.00 | 0.02 | 1 | 0.0268 | 168 B | 1.00 |
112+
| Invoke_CompiledFast | 40.59 ns | 0.157 ns | 0.123 ns | 1.01 | 0.01 | 1 | 0.0166 | 104 B | 0.62 |
113+
| Invoke_Compiled | 1,142.12 ns | 11.877 ns | 14.586 ns | 28.35 | 0.48 | 2 | 0.0420 | 264 B | 1.57 |
115114

116115

117116
### Manually composed expression with parameters and closure
@@ -127,21 +126,20 @@ var expr = Expression.Lambda(
127126

128127
Compiling expression:
129128

130-
| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
131-
| ---------------------------- | --------: | --------: | --------: | --------: | ----: | ------: | -----: | -----: | --------: | ----------: |
132-
| Compile_SystemExpression | 89.076 us | 2.6699 us | 7.6605 us | 85.180 us | 28.12 | 3.05 | 0.7324 | 0.4883 | 4.74 KB | 3.41 |
133-
| CompileFast_SystemExpression | 3.138 us | 0.0550 us | 0.0565 us | 3.118 us | 0.99 | 0.03 | 0.2213 | 0.2136 | 1.39 KB | 1.00 |
134-
| CompileFast_LightExpression | 3.180 us | 0.0602 us | 0.0591 us | 3.163 us | 1.00 | 0.00 | 0.2213 | 0.2136 | 1.39 KB | 1.00 |
135-
129+
| Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio |
130+
| ---------------------------- | ---------: | --------: | --------: | ----: | ------: | ---: | -----: | -----: | --------: | ----------: |
131+
| CompileFast_LightExpression | 3.107 us | 0.0562 us | 0.0498 us | 0.99 | 0.02 | 1 | 0.1755 | 0.1678 | 1.08 KB | 1.00 |
132+
| CompileFast_SystemExpression | 3.126 us | 0.0288 us | 0.0256 us | 1.00 | 0.01 | 1 | 0.1755 | 0.1678 | 1.08 KB | 1.00 |
133+
| Compile_SystemExpression | 103.948 us | 1.9593 us | 2.5477 us | 33.26 | 0.84 | 2 | 0.7324 | 0.4883 | 4.74 KB | 4.40 |
136134

137135
Invoking the compiled delegate compared to the normal delegate and the direct call:
138136

139-
| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
140-
| ----------------------------- | -------: | --------: | --------: | -------: | ----: | ------: | -----: | --------: | ----------: |
141-
| DirectCall | 8.388 ns | 0.2655 ns | 0.7575 ns | 8.092 ns | 1.00 | 0.07 | 0.0051 | 32 B | 1.00 |
142-
| Compiled_SystemExpression | 9.474 ns | 0.1870 ns | 0.4105 ns | 9.381 ns | 1.10 | 0.05 | 0.0051 | 32 B | 1.00 |
143-
| CompiledFast_SystemExpression | 8.575 ns | 0.1624 ns | 0.1440 ns | 8.517 ns | 1.00 | 0.02 | 0.0051 | 32 B | 1.00 |
144-
| CompiledFast_LightExpression | 8.584 ns | 0.0776 ns | 0.0862 ns | 8.594 ns | 1.00 | 0.00 | 0.0051 | 32 B | 1.00 |
137+
| Method | Mean | Error | StdDev | Ratio | Rank | Gen0 | Allocated | Alloc Ratio |
138+
| ----------------------------- | -------: | -------: | -------: | ----: | ---: | -----: | --------: | ----------: |
139+
| DirectCall | 10.19 ns | 0.108 ns | 0.085 ns | 1.00 | 1 | 0.0051 | 32 B | 1.00 |
140+
| CompiledFast_LightExpression | 10.70 ns | 0.089 ns | 0.070 ns | 1.05 | 2 | 0.0051 | 32 B | 1.00 |
141+
| CompiledFast_SystemExpression | 10.91 ns | 0.071 ns | 0.066 ns | 1.07 | 2 | 0.0051 | 32 B | 1.00 |
142+
| Compiled_SystemExpression | 11.59 ns | 0.098 ns | 0.081 ns | 1.14 | 3 | 0.0051 | 32 B | 1.00 |
145143

146144

147145
### FastExpressionCompiler.LightExpression.Expression vs System.Linq.Expressions.Expression
@@ -169,20 +167,18 @@ Hopefully you are checking the expression arguments yourself and not waiting for
169167
170168
Creating the expression:
171169

172-
| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
173-
| -------------------------------------- | ---------: | -------: | -------: | ---------: | ----: | ------: | -----: | --------: | ----------: |
174-
| Create_SystemExpression | 1,110.9 ns | 22.19 ns | 62.23 ns | 1,086.1 ns | 7.25 | 0.56 | 0.2060 | 1304 B | 2.63 |
175-
| Create_LightExpression | 153.7 ns | 3.14 ns | 8.61 ns | 150.5 ns | 1.00 | 0.08 | 0.0789 | 496 B | 1.00 |
176-
| Create_LightExpression_with_intrinsics | 161.0 ns | 2.80 ns | 2.19 ns | 161.0 ns | 1.05 | 0.06 | 0.0777 | 488 B | 0.98 |
170+
| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Rank | Gen0 | Allocated | Alloc Ratio |
171+
| ----------------------- | ---------: | -------: | -------: | ---------: | ----: | ------: | ---: | -----: | --------: | ----------: |
172+
| Create_LightExpression | 156.6 ns | 3.19 ns | 8.18 ns | 151.9 ns | 1.00 | 0.07 | 1 | 0.0827 | 520 B | 1.00 |
173+
| Create_SystemExpression | 1,065.0 ns | 14.24 ns | 11.89 ns | 1,069.3 ns | 6.82 | 0.34 | 2 | 0.2060 | 1304 B | 2.51 |
177174

178175
Creating and compiling:
179176

180-
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
181-
| ---------------------------------------------------- | ---------: | --------: | ---------: | ----: | ------: | -----: | -----: | --------: | ----------: |
182-
| Create_SystemExpression_and_Compile | 212.157 us | 4.2180 us | 11.4036 us | 44.77 | 3.31 | 0.9766 | 0.4883 | 7.15 KB | 2.95 |
183-
| Create_SystemExpression_and_CompileFast | 6.656 us | 0.1322 us | 0.3065 us | 1.40 | 0.10 | 0.5188 | 0.4883 | 3.27 KB | 1.35 |
184-
| Create_LightExpression_and_CompileFast | 4.751 us | 0.0947 us | 0.2411 us | 1.00 | 0.07 | 0.3815 | 0.3662 | 2.42 KB | 1.00 |
185-
| CreateLightExpression_and_CompileFast_with_intrinsic | 4.604 us | 0.0918 us | 0.1915 us | 0.97 | 0.06 | 0.3815 | 0.3662 | 2.35 KB | 0.97 |
177+
| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio |
178+
| --------------------------------------- | ---------: | --------: | --------: | ---------: | ----: | ------: | ---: | -----: | -----: | --------: | ----------: |
179+
| Create_LightExpression_and_CompileFast | 4.957 us | 0.0986 us | 0.2362 us | 4.913 us | 1.00 | 0.07 | 1 | 0.3510 | 0.3052 | 2.15 KB | 1.00 |
180+
| Create_SystemExpression_and_CompileFast | 6.518 us | 0.1889 us | 0.5541 us | 6.300 us | 1.32 | 0.13 | 2 | 0.4578 | 0.4272 | 2.97 KB | 1.38 |
181+
| Create_SystemExpression_and_Compile | 205.000 us | 4.0938 us | 7.3819 us | 206.353 us | 41.44 | 2.45 | 3 | 0.9766 | 0.4883 | 7.15 KB | 3.33 |
186182

187183

188184
## Difference between FastExpressionCompiler and FastExpressionCompiler.LightExpression
@@ -300,17 +296,91 @@ FEC V3 has added powerful diagnostics and code generation tools.
300296

301297
You may pass the optional `CompilerFlags.EnableDelegateDebugInfo` into the `CompileFast` methods.
302298

303-
`EnableDelegateDebugInfo` adds the diagnostic info into the compiled delegate including its source Expression and C# code.
304-
Can be used as following:
299+
`EnableDelegateDebugInfo` adds the diagnostic info into the compiled delegate including its source Expression and compiled IL code.
300+
301+
It can be used as following:
302+
303+
```cs
304+
System.Linq.Expressions.Expression<Func<int, Func<int>>> e =
305+
n => () => n + 1;
306+
var f = e.CompileFast(flags: CompilerFlags.EnableDelegateDebugInfo);
307+
var d = f.TryGetDebugInfo();
308+
d.PrintExpression();
309+
d.PrintCSharp();
310+
d.PrintIL(); // available in NET8+
311+
```
312+
313+
<details><summary>Expand to see the output of the above code...</summary>
314+
315+
316+
Output of `d.PrintExpression()` is the valid C#:
305317

306318
```cs
307-
var f = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo);
308-
var di = f.Target as IDelegateDebugInfo;
309-
Asserts.IsNotNull(di.Expression);
310-
Asserts.IsNotNull(di.ExpressionString);
311-
Asserts.IsNotNull(di.CSharpString);
319+
var p = new ParameterExpression[1]; // the parameter expressions
320+
var e = new Expression[3]; // the unique expressions
321+
var expr = Lambda<Func<int, Func<int>>>(
322+
e[0]=Lambda<Func<int>>(
323+
e[1]=MakeBinary(ExpressionType.Add,
324+
p[0]=Parameter(typeof(int), "n"),
325+
e[2]=Constant(1)), new ParameterExpression[0]),
326+
p[0 // (int n)
327+
]);
312328
```
313329

330+
Output of `d.PrintCSharp()` is the valid C#:
331+
332+
```cs
333+
var @cs = (Func<int, Func<int>>)((int n) => //Func<int>
334+
(Func<int>)(() => //int
335+
n + 1));
336+
```
337+
338+
Output of `d.PrintIL()` (includes the IL of the nested lambda):
339+
340+
```
341+
<Caller>
342+
0 ldarg.0
343+
1 ldfld object[] ExpressionCompiler.ArrayClosure.ConstantsAndNestedLambdas
344+
6 stloc.0
345+
7 ldloc.0
346+
8 ldc.i4.0
347+
9 ldelem.ref
348+
10 stloc.1
349+
11 ldloc.1
350+
12 ldc.i4.1
351+
13 newarr object
352+
18 stloc.2
353+
19 ldloc.2
354+
20 stfld object[] ExpressionCompiler.NestedLambdaForNonPassedParams.NonPassedParams
355+
25 ldloc.2
356+
26 ldc.i4.0
357+
27 ldarg.1
358+
28 box int
359+
33 stelem.ref
360+
34 ldloc.1
361+
35 ldfld object ExpressionCompiler.NestedLambdaForNonPassedParams.NestedLambda
362+
40 ldloc.2
363+
41 ldloc.1
364+
42 ldfld object[] ExpressionCompiler.NestedLambdaForNonPassedParamsWithConstants.ConstantsAndNestedLambdas
365+
47 newobj ExpressionCompiler.ArrayClosureWithNonPassedParams(System.Object[], System.Object[])
366+
52 call Func<int> ExpressionCompiler.CurryClosureFuncs.Curry(System.Func`2[FastExpressionCompiler.LightExpression.ExpressionCompiler+ArrayClosure,System.Int32], ArrayClosure)
367+
57 ret
368+
</Caller>
369+
<0_nested_in_Caller>
370+
0 ldarg.0
371+
1 ldfld object[] ExpressionCompiler.ArrayClosureWithNonPassedParams.NonPassedParams
372+
6 ldc.i4.0
373+
7 ldelem.ref
374+
8 unbox.any int
375+
13 ldc.i4.1
376+
14 add
377+
15 ret
378+
</0_nested_in_Caller>
379+
```
380+
381+
</details>
382+
383+
314384
### ThrowOnNotSupportedExpression and NotSupported_ flags
315385

316386
FEC V3.1 has added the compiler flag `CompilerFlags.ThrowOnNotSupportedExpression`.

btdebug.bat

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
@echo off
2+
3+
echo:
4+
echo:## Running TESTS on the Latest Supported .NET...
5+
echo:
6+
dotnet run -p:DevMode=true -f:net9.0 -c:Debug --project test/FastExpressionCompiler.TestsRunner/FastExpressionCompiler.TestsRunner.csproj
7+
if %ERRORLEVEL% neq 0 goto :error
8+
9+
echo:
10+
echo:## Finished: TESTS
11+
12+
echo:# Finished: ALL
13+
echo:
14+
exit /b 0
15+
16+
:error
17+
echo:
18+
echo:## :-( Failed with ERROR: %ERRORLEVEL%
19+
echo:
20+
exit /b %ERRORLEVEL%

src/Directory.Build.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
</ItemGroup>
3434

3535
<PropertyGroup>
36+
<Features>strict</Features>
3637
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
3738
</PropertyGroup>
3839

0 commit comments

Comments
 (0)