Coder Social home page Coder Social logo

dadhi / fastexpressioncompiler Goto Github PK

View Code? Open in Web Editor NEW
1.1K 35.0 79.0 8.1 MB

Fast Compiler for C# Expression Trees and the lightweight LightExpression alternative. Diagnostic and code generation tools for the expressions.

License: MIT License

C# 99.76% Batchfile 0.21% PowerShell 0.03%
expression-tree dryioc delegate il-optimizations closure performance benchmark compiler delegates code-generation

fastexpressioncompiler's Introduction

FastExpressionCompiler

logo

latest release notes Windows buildlicense

Targets .NET 6+, .NET 4.5+, .NET Standard 2.0+

NuGet packages:

  • FastExpressionCompiler NuGet Badge
    • sources package: FastExpressionCompiler.src NuGet Badge
    • sources with the public code made internal: FastExpressionCompiler.Internal.src NuGet Badge
  • FastExpressionCompiler.LightExpression NuGet Badge
    • sources package: FastExpressionCompiler.LightExpression.src NuGet Badge
    • sources with the public code made internal: FastExpressionCompiler.LightExpression.Internal.src NuGet Badge

The project was originally a part of the DryIoc, so check it out ;-)

The problem

ExpressionTree compilation is used by the wide variety of tools, e.g. IoC/DI containers, Serializers, ORMs and OOMs. But Expression.Compile() is just slow. Moreover the compiled delegate may be slower than the manually created delegate because of the reasons:

TL;DR;

Expression.Compile creates a DynamicMethod and associates it with an anonymous assembly to run it in a sand-boxed environment. This makes it safe for a dynamic method to be emitted and executed by partially trusted code but adds some run-time overhead.

See also a deep dive to Delegate internals.

The solution

The FastExpressionCompiler .CompileFast() extension method is 10-40x times faster than .Compile().
The compiled delegate may be in some cases a lot faster than the one produced by .Compile().

Note: The actual performance may vary depending on the multiple factors: platform, how complex is expression, does it have a closure, does it contain nested lambdas, etc.

In addition, the memory consumption taken by the compilation will be much smaller (check the Allocated column in the benchmarks below).

Benchmarks

Updated to .NET 8.0

BenchmarkDotNet v0.13.10, Windows 11 (10.0.22621.2428/22H2/2022Update/SunValley2)
11th Gen Intel Core i7-1185G7 3.00GHz, 1 CPU, 8 logical and 4 physical cores
.NET SDK 8.0.100-rc.2.23502.2
[Host]     : .NET 8.0.0 (8.0.23.47906), X64 RyuJIT AVX2
DefaultJob : .NET 8.0.0 (8.0.23.47906), X64 RyuJIT AVX2

Hoisted expression with the constructor and two arguments in closure

var a = new A();
var b = new B();
Expression<Func<X>> e = () => new X(a, b);

Compiling expression:

Method Mean Error StdDev Median Ratio RatioSD Gen0 Gen1 Allocated Alloc Ratio
Compile 121.969 us 2.4180 us 5.6040 us 120.830 us 35.77 2.46 0.7324 - 4.49 KB 2.92
CompileFast 3.406 us 0.0677 us 0.1820 us 3.349 us 1.00 0.00 0.2441 0.2365 1.54 KB 1.00

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

Method Mean Error StdDev Median Ratio RatioSD Gen0 Allocated Alloc Ratio
DirectConstructorCall 5.734 ns 0.1501 ns 0.2745 ns 5.679 ns 0.86 0.05 0.0051 32 B 1.00
CompiledLambda 6.857 ns 0.1915 ns 0.5434 ns 6.704 ns 1.01 0.09 0.0051 32 B 1.00
FastCompiledLambda 6.746 ns 0.1627 ns 0.1442 ns 6.751 ns 1.00 0.00 0.0051 32 B 1.00

Hoisted expression with the static method and two nested lambdas and two arguments in closure

var a = new A();
var b = new B();
Expression<Func<X>> getXExpr = () => CreateX((aa, bb) => new X(aa, bb), new Lazy<A>(() => a), b);

Compiling expression:

Method Mean Error StdDev Ratio RatioSD Gen0 Gen1 Allocated Alloc Ratio
Compile 442.02 us 8.768 us 21.998 us 40.00 2.34 1.9531 0.9766 12.04 KB 2.61
CompileFast 11.06 us 0.221 us 0.441 us 1.00 0.00 0.7324 0.7019 4.62 KB 1.00

Invoking compiled delegate comparing to direct method call:

Method Mean Error StdDev Ratio RatioSD Gen0 Allocated Alloc Ratio
DirectMethodCall 35.51 ns 0.783 ns 1.308 ns 0.86 0.08 0.0267 168 B 1.62
Invoke_Compiled 1,096.15 ns 21.507 ns 41.437 ns 27.15 2.75 0.0420 264 B 2.54
Invoke_CompiledFast 37.65 ns 1.466 ns 4.299 ns 1.00 0.00 0.0166 104 B 1.00

Manually composed expression with parameters and closure

var a = new A();
var bParamExpr = Expression.Parameter(typeof(B), "b");
var expr = Expression.Lambda(
    Expression.New(_ctorX,
        Expression.Constant(a, typeof(A)), bParamExpr),
    bParamExpr);

Compiling expression:

Method Mean Error StdDev Median Ratio RatioSD Gen0 Gen1 Allocated Alloc Ratio
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
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
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

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

Method Mean Error StdDev Median Ratio RatioSD Gen0 Allocated Alloc Ratio
DirectCall 8.388 ns 0.2655 ns 0.7575 ns 8.092 ns 1.00 0.07 0.0051 32 B 1.00
Compiled_SystemExpression 9.474 ns 0.1870 ns 0.4105 ns 9.381 ns 1.10 0.05 0.0051 32 B 1.00
CompiledFast_SystemExpression 8.575 ns 0.1624 ns 0.1440 ns 8.517 ns 1.00 0.02 0.0051 32 B 1.00
CompiledFast_LightExpression 8.584 ns 0.0776 ns 0.0862 ns 8.594 ns 1.00 0.00 0.0051 32 B 1.00

FastExpressionCompiler.LightExpression.Expression vs System.Linq.Expressions.Expression

FastExpressionCompiler.LightExpression.Expression is the lightweight version of System.Linq.Expressions.Expression. It is designed to be a drop-in replacement for the System Expression - just install the FastExpressionCompiler.LightExpression package instead of FastExpressionCompiler and replace the usings

using System.Linq.Expressions;
using static System.Linq.Expressions.Expression;

with

using static FastExpressionCompiler.LightExpression.Expression;
namespace FastExpressionCompiler.LightExpression.UnitTests

You may look at it as a bare-bone wrapper for the computation operation node which helps you to compose the computation tree (without messing with the IL emit directly). It won't validate operations compatibility for the tree the way System.Linq.Expression does it, and partially why it is so slow. Hopefully you are checking the expression arguments yourself and not waiting for the Expression exceptions to blow-up.

Sample expression

Creating the expression:

Method Mean Error StdDev Ratio RatioSD Gen0 Allocated Alloc Ratio
Create_SystemExpression 1,039.5 ns 20.75 ns 45.98 ns 8.29 0.50 0.2060 1304 B 2.63
Create_LightExpression 125.7 ns 2.46 ns 5.99 ns 1.00 0.00 0.0789 496 B 1.00
Create_LightExpression_with_intrinsics 130.0 ns 2.47 ns 6.25 ns 1.04 0.07 0.0777 488 B 0.98

Creating and compiling:

Method Mean Error StdDev Ratio RatioSD Gen0 Gen1 Allocated Alloc Ratio
Create_SystemExpression_and_Compile 159.184 us 2.9731 us 7.1235 us 37.34 1.65 0.9766 0.4883 7.4 KB 3.06
Create_SystemExpression_and_CompileFast 5.923 us 0.0996 us 0.1771 us 1.34 0.05 0.5188 0.5035 3.27 KB 1.35
Create_LightExpression_and_CompileFast 4.399 us 0.0484 us 0.0453 us 1.00 0.00 0.3815 0.3662 2.42 KB 1.00
CreateLightExpression_and_CompileFast_with_intrinsic 4.384 us 0.0835 us 0.0697 us 1.00 0.02 0.3815 0.3662 2.35 KB 0.97

Difference between FastExpressionCompiler and FastExpressionCompiler.LightExpression

FastExpressionCompiler

  • Provides the CompileFast extension methods for the System.Linq.Expressions.LambdaExpression.

FastExpressionCompiler.LightExpression

  • Provides the CompileFast extension methods for FastExpressionCompiler.LightExpression.LambdaExpression.
  • Provides the drop-in expression replacement with the less consumed memory and the faster construction at the cost of the less validation.
  • Includes its own ExpressionVisitor.
  • Supports ToExpression method to convert back to the System.Linq.Expressions.Expression.

Both FastExpressionCompiler and FastExpressionCompiler.LightExpression

  • Support ToCSharpString() method to output the compile-able C# code represented by expression.
  • Support ToExpressionString() method to output the expression construction C# code, so given the expression object you'll get e.g. Expression.Lambda(Expression.New(...)).

Who's using it

Marten, Rebus, StructureMap, Lamar, ExpressionToCodeLib, NServiceBus, LINQ2DB, MapsterMapper

Considering: Moq, Apex.Serialization

How to use

Install from the NuGet and add the using FastExpressionCompiler; and replace the call to the .Compile() with the .CompileFast() extension method.

Note: CompileFast has an optional parameter bool ifFastFailedReturnNull = false to disable fallback to Compile.

Examples

Hoisted lambda expression (created by the C# Compiler):

var a = new A(); var b = new B();
Expression<Func<X>> expr = () => new X(a, b);

var getX = expr.CompileFast();
var x = getX();

Manually composed lambda expression:

var a = new A();
var bParamExpr = Expression.Parameter(typeof(B), "b");
var expr = Expression.Lambda(
    Expression.New(_ctorX,
        Expression.Constant(a, typeof(A)), bParamExpr),
    bParamExpr);

var f = expr.CompileFast();
var x = f(new B());

Note: You may simplify Expression usage and enable faster refactoring with the C# using static statement:

using static System.Linq.Expressions.Expression;
// or
// using static FastExpressionCompiler.LightExpression.Expression;

var a = new A();
var bParamExpr = Parameter(typeof(B), "b");
var expr = Lambda(
    New(_ctorX, Constant(a, typeof(A)), bParamExpr),
    bParamExpr);

var f = expr.CompileFast();
var x = f(new B());

How it works

The idea is to provide the fast compilation for the supported expression types and fallback to the system Expression.Compile() for the not supported types:

What's not supported yet

FEC does not support yet:

  • Quote
  • Dynamic
  • RuntimeVariables
  • DebugInfo
  • MemberInit with the MemberMemberBinding and the ListMemberBinding binding types
  • NewArrayInit multi-dimensional array initializer is not supported yet

To find what nodes are not supported in your expression you may use the technic described below in the Diagnostics section.

The compilation is done by traversing the expression nodes and emitting the IL. The code is tuned for the performance and the minimal memory consumption.

The expression is traversed twice:

  • 1st round is to collect the constants and nested lambdas into the closure objects.
  • 2nd round is to emit the IL code and create the delegate using the DynamicMethod.

If visitor finds the not supported expression node or the error condition, the compilation is aborted, and null is returned enabling the fallback to System .Compile().

Diagnostics and Code Generation

FEC V3 has added powerful diagnostics and code generation tools.

Diagnostics

You may pass the optional CompilerFlags.EnableDelegateDebugInfo into the CompileFast methods.

EnableDelegateDebugInfo adds the diagnostic info into the compiled delegate including its source Expression and C# code. Can be used as following:

var f = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo);
var di = f.Target as IDelegateDebugInfo;
Assert.IsNotNull(di.Expression);
Assert.IsNotNull(di.ExpressionString);
Assert.IsNotNull(di.CSharpString);

ThrowOnNotSupportedExpression and NotSupported_ flags

FEC V3.1 has added the compiler flag CompilerFlags.ThrowOnNotSupportedExpression. When passed to CompileFast(flags: CompilerFlags.ThrowOnNotSupportedExpression) and the expression contains not (yet) supported Expression node the compilation will throw the exception instead of returning null.

To get the whole list of the not yet supported cases you may check in Result.NotSupported_ enum values.

Code Generation

The Code Generation capabilities are available via the ToCSharpString and ToExpressionString extension methods.

Note: When converting the source expression to either C# code or to the Expression construction code you may find the // NOT_SUPPORTED_EXPRESSION comments marking the not supported yet expressions by FEC. So you may test the presence or absence of this comment.

Additional optimizations

  1. Using FastExpressionCompiler.LightExpression.Expression instead of System.Linq.Expressions.Expression for the faster expression creation.
  2. Using .TryCompileWithPreCreatedClosure and .TryCompileWithoutClosure methods when you know the expression at hand and may skip the first traversing round, e.g. for the "static" expression which does not contain the bound constants. Note: You cannot skip the 1st round if the expression contains the Block, Try, or Goto expressions.

Bitten Ice Pop icon icon by Icons8

fastexpressioncompiler's People

Contributors

dadhi avatar denis-tsv avatar eamonnerbonne avatar egopingvina avatar emiliovmq avatar exyi avatar havunen avatar iarovyi avatar jogibear9988 avatar lordzoltan avatar macewindu avatar marchingcube avatar marluwe avatar maximusya avatar ovska avatar samcragg avatar seregazh avatar ssupr avatar stevewilkes avatar tyoungsl avatar warpten avatar z4kn4fein avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fastexpressioncompiler's Issues

Support for ExpressionInfo NewArray, MemberInit, ArrayIndex, Convert, MemberAccess

To support such kind of expressions:

      var expr = ExpressionInfo.Lambda<Func<object[], object>>(
            ExpressionInfo.MemberInit(
                ExpressionInfo.New(_ctorOfA,
                    ExpressionInfo.New(_ctorOfB),
                    ExpressionInfo.Convert(
                        ExpressionInfo.ArrayIndex(stateParamExpr, ExpressionInfo.Constant(11)), 
                        typeof(string)),
                    ExpressionInfo.NewArrayInit(typeof(ID[]),
                        ExpressionInfo.New(_ctorOfD1),
                        ExpressionInfo.New(_ctorOfD2))),
                ExpressionInfo.Bind(_propAProp,
                    ExpressionInfo.New(_ctorOfP,
                        ExpressionInfo.New(_ctorOfB))),
                ExpressionInfo.Bind(_fieldABop,
                    ExpressionInfo.New(_ctorOfB))),
            stateParamExpr);

Better strategy to split code between Expression and ExpressionInfo

Current approach is mix and max with a lot of copy-paste. Hopefully, we can devise a strategy:

1 Split the code completely, may be even move to separate file and package.

Pros:

  • Simple to achieve
  • More performant cause requires less run-time switches
  • Flexible in regards of adding new features into ExprInfo
  • Less noise for those who don't need ExprInfo

Cons:

  • Changes and Fixes should be copied between two
  • Two files/packages to support

2 Abstract over both and have single source code for both

Pros:

  • One compact code-base, no need to copy changes and fixes
  • One package to support

Cons:

  • Harder to-do and may impact performance due switch logic
  • Likely, less flexible when adding the new features or changing ExprInfo alone

Conversion to Nullable<T> throws InvalidProgramException

class Program
{
    static void Main(string[] args)
    {
        Expression<Func<decimal?>> expression = () => 42m;
        decimal? answer = expression.CompileFast().Invoke();
    }
}

.NET Core 2.0; FastExpressionCompiler 1.4.0:

Unhandled Exception: System.InvalidProgramException: Common Language Runtime detected an invalid program.
   at (Closure`1 )
   at Program.Main(String[] args) in C:\Users\panyushkind\source\repos\FastExpressionCompilerFails\FastExpressionCompilerFails\Program.cs:line 10

And also with

Expression<Func<int?>> expression = () => 42;
int? answer = expression.CompileFast().Invoke();

application will crash silently, without any stacktrace.

Fix dotnet test (NUnit) in build script

For the moment running dotnet test produces this error:

No test discoverer is registered to perform discovery of test cases. Register a test discoverer and try again.

CompileFast crash with ref parameter

A crashing example:

struct Bla { public int Hmm; }
public delegate void Setter<T>(ref T obj, int value);

void GoAndCrash()
{
    var objRef = Expression.Parameter(typeof(Bla).MakeByRefType());
    var objVal = Expression.Parameter(typeof(int));
    var prop = Expression.PropertyOrField(objRef,"Hmm");
    var body = Expression.Assign(prop, objVal);
    var lambda = Expression.Lambda<Setter<Bla>>(body,objRef,objVal);
    
    var compiledA = lambda.Compile();

    //compiledB.Dump();
    Bla example = default;

    compiledA(ref example, 7);

    Console.WriteLine(example.Hmm);

    var compiledB = FastExpressionCompiler.ExpressionCompiler.CompileFast<Setter<Bla>>(lambda, true);
    //...
}

Expected behavior: returns null or falls-back to Expression.Compile, depending on the bool parameter controlling unsupported exceptions.

System.InvalidProgramException

@dadhi

I newly got this Exception on testing server.
"
System.InvalidProgramException: Common Language Runtime detected an invalid program.
at (Closure5 , TimeSlice ) at System.Linq.Enumerable.WhereEnumerableIterator1.MoveNext()
at System.Linq.Enumerable.Any[TSource](IEnumerable1 source) at Csh.Hcis.GC.VueScheduler.Common.Extensions.LinqExtensions.<AnyAsync2>d__111.MoveNext()
"

Here is the code:
20171121155927

Here is the compiled delegate, it has 5 same variables (strange?).
20171121155741

Is there any clue?

Allow ExpressionInfo.MemberInit for non-New expressions

Classical Expression.MemberInfo represents initializer expr:

() => new X { A = a }

Therefore, the first part must be a New expression.

On the other hand, it is very common, e.g. in Serializers, OOMappers, to initialize already created instance:

() => 
{
    x = GetX();
    x.A = a;
    return x;
}

We cannot write such thing using Expression.MemberInit but we are free to do that with FEC ExpressionInfo.MemberInit.

The emitted code in both cases is the same.

In addition, it may be much easy option than implementing #17.

TryExpression support

Now when we have initial Block support, we may support compilation of this:

TryExpression tryCatchExpr =
    Expression.TryCatch(
        Expression.Block(
            Expression.Throw(Expression.Constant(new DivideByZeroException())),
            Expression.Constant("Try block")
        ),
        Expression.Catch(
            typeof(DivideByZeroException),
            Expression.Constant("Catch block")
        )
    );

StrongNaming missing

The fact there is no strong name for this kind of assembly avoids us to use it from other strong named assemblies.

Race condition in nested lambda expression with closure

At the moment nested lambda has its own closure object. Which fields should be set from the outer closure, if nested lambda uses outer closed value, including outer parameters. Given the deeply nested lambdas, their set operations may be interleaved by other threads, those producing unexpected results.

Final solution (comments are just for history):

  • Root expression has single pre-constructed closure instance
  • Nested lambda expression has its closure constructed each time when lambda is loaded on stack. The nested closure is constructed from outer parameters, closed values, and more deeply nested lambdas. The nested lambda parameters are extended with first closure parameter, then the original delegate type is curry-ied by passing the constructed closure.

Support all types and operations from System.Numerics

#r "D:\dzmitry\src\clr\src\langs\csharp\packages\FastExpressionCompiler.1.6.0\lib\net45\FastExpressionCompiler.dll"
. #r "System.Numerics.Vectors.dll"
. #r "System.Numerics.dll"
. using System;
. using System.Collections.Generic;
. using System.Linq;
. using System.Linq.Expressions;
. using System.Text;
. using System.Threading.Tasks;
. using System.Numerics;
. using FastExpressionCompiler;
.
.
.
.
. double z() => 42.2;
. double x = 123.3;
. int b = 13;
. Expression<Func> a = () => ((((~b + 1 * x ))))- 1 / 2 % 13 + 1 * (4 & b) - 1 / 8 + (1 | 0) / 16 / x + 1_000 + (!(x == b) ? 1 : 2) + (1 >> 20) - (b << 30) - float.MaxValue;
. Console.WriteLine(a.Compile().Invoke());
. Console.WriteLine(a.CompileFast().Invoke());
.
. Expression<Func> bb = ()=> Vector2.One - new Vector2(1, 1);
. Console.WriteLine(bb.Compile().Invoke());
. Console.WriteLine(bb.CompileFast().Invoke());
-3.40282346638529E+38
-3.40282346638529E+38
<0, 0>
Common Language Runtime detected an invalid program.

String constant comparisons fail

The func created by compiling the following expression:

Expression<Func<string, bool>> expr = str => str == "Hello";

...does not work as expected. This is because equality comparisons created by EmitComparison always use OpCodes.Ceq, which does not work for strings (and possibly other reference types - I haven't checked).

This StackOverflow answer has an example of emitting equality comparisons for reference types.

I'd be happy to submit a pull request, but my VS / ReSharper / dotnet all refuse to recognise the NUnit tests!

Combine outer and nested delegates into one

Idea is to generate single delegate with all the nested lambdas split via labels and gotos.

Why not? We may opt-in this behavior to make client decide.

Pros:

  1. One round of dynamic method create, emit, and delegate creation. It is likely to improve perf significally for delegate with multi nested lambdas.

Cons:

  1. More complex emitting code. It may be not an issue, cause the code for nested lambda compilation is already complex.

Allow Expression as child of multi-child ExpressionInfo

Today the ExpressionInfo child node may be other ExpressionInfo only, except for the ParameterExpression (for conveniece reasons).

Ideally, I want to benifit from ExpressionInfo even for the part of expression. So, such an expression can be composed and compiled:

var createA = ExpressionInfo.Lambda<Func<A>>()
    ExpressionInfo.New(ctorOfA, 
        Expression.New(ctorOfB)))
.CompileFast();

Diagnostics for emitted IL code

The FEC may generate different IL and therefore may display different execution performance for the result delegate.

There is also question of correctness and easy of debugging.

It also should simplify new features implementation, etc. etc.

Really strange, that the project was able to get to this point without such thing.

For all these purposes we need an ability to dump the resulting IL, so it can be compared with standard IL produced by C# compiler.

Execution slow-down, or incorrect use?

Hi,

In the compiled delegates below (adding two ints), using FastExpressionCompiler the execution time is consistently 16% and 65% slower than using standard BCL expressions. Any idea why, or am I using it incorrectly?

Thanks,
Kristian

ETSumBCL: 164 ms
ETSumCompileFastInfo: 191 ms (+16%)
ETSumCompileFast: 271 ms (+65%)

// using Fast = FastExpressionCompiler;
// using FastPEI = FastExpressionCompiler.ParameterExpressionInfo;

            Time("Expression", ETSum());
            Time("Expression Compile Fast Info", ETSumCompileFastInfo());
            Time("Expression Compile Fast", ETSumCompileFast());

             Func<int, int, int> ETSum()
            {
                var aExp = Expression.Parameter(typeof(int), "a");
                var bExp = Expression.Parameter(typeof(int), "b");

                return Expression.Lambda<Func<int, int, int>>(
                    Expression.Add(aExp, bExp), aExp, bExp).Compile();
            }

            Func<int, int, int> ETSumCompileFastInfo()
            {
                var aExp = FastPEI.Parameter(typeof(int), "a");
                var bExp = FastPEI.Parameter(typeof(int), "b");

                return Fast.ExpressionCompiler.CompileFast<Func<int, int, int>>(
                    FastPEI.Lambda(Expression.Add(aExp, bExp), aExp, bExp)
                    );
            }

            Func<int, int, int> ETSumCompileFast()
            {
                var aExp = Expression.Parameter(typeof(int), "a");
                var bExp = Expression.Parameter(typeof(int), "b");

                return Fast.ExpressionCompiler.CompileFast<Func<int, int, int>>(
                    Expression.Lambda(Expression.Add(aExp, bExp), aExp, bExp)
                    );
            }

        private static void Time(String name, Func<Int32, Int32, Int32> fn)
        {
            int result = 0;
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (Int32 index = 0; index <= 100000000; index++)
            {
                result += fn(index, 1);
            }
            sw.Stop();
            Console.WriteLine($"{name}: {sw.ElapsedMilliseconds} ms{result.ToString().Substring(0, 0)}");
        }

Diagnostics for not supported expression

Currently the only "diagnostic" is null returned from CompileFast(true).

Ideally I want to know what sub-expression caused this null, and as much context as possible.

Improve IL for ValueTypes

Current implementation is sub-optimal:

  1. It uses Boxing where it is not always needed
  2. It defines additional variable instead of using the one higher in expression tree

Have you considered wrappers around ILGenerator?

So perhaps this is all old-hat to you, but I can imagine that poking around IL is timeconsuming. Have you considered things like gremit or sigil?

Those might (certainly initially) help you broaden the base of the generated il; and certainly sigil takes care of some really boring IL-related minutiae (such as short vs. long for instructions and so on).

[Challenge] ExpressionInfo for async await pattern

Expression does not suport async await probably due whole lot of machinery required for its full implementation.

But FEC is just a library, not a .Net framework to cover everything.
In addition FEC has its own ExpressionInfo.
We can start from simple cases, e.g. async lambdas and methods, and await the call or property.

Source code NuGet package to simplify FEC embedding without additional dependency

Currently FEC is just a one cs file, and given its current usage in DryIoc, and also Marten, StructureMap - the cs file is just added into the consumer project.

To simplify this scenario, we can provide the source code / content files NuGet package. The package may be installed once and then excluded from the dependencies. Otherwise, the person need to look into GitHub to get the source code.

cast int->ulong creates invalid delegates.

ExpressionCompiler.Compile( () => 0UL == (ulong)"x".Length)();  //NullReferenceException
ExpressionCompiler.Compile( () => 0UL == (ulong)DateTime.Now.Day)();//Process dies

The second one is scary; there's a chance this is a security issue in .NET. It reports that...

The process was terminated due to an internal error in the .NET Runtime at IP 00007FFD35C25C22 (00007FFD35B90000) with exit code 80131506.

I would have expected an InvalidProgramException or something rather than that, but I'm not too familiar with the internals, so who knows...

Cannot run unit Tests with VS 2017.6 NUnitTest Adapter 3.9

Tests are visible but do not run

image

Tried both bitnesses:

image

Seems this root cause:

[10-Mar-18 11:07:51 Informational] Executing all tests in type 'FastExpressionCompiler.IssueTests.Issue55_CompileFast_crash_with_ref_parameter'
[10-Mar-18 11:07:51 Informational] ------ Run test started ------
[10-Mar-18 11:08:51 Error] Microsoft.VisualStudio.TestPlatform.ObjectModel.TestPlatformException: Testhost process exited with error: It was not possible to find any compatible framework version
The specified framework 'Microsoft.NETCore.App', version '1.1.2' was not found.
  - Check application dependencies and target a framework version installed at:
      \
  - Alternatively, install the framework version '1.1.2'.

   at Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.ProxyOperationManager.SetupChannel(IEnumerable`1 sources, CancellationToken cancellationToken)
   at Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.ProxyExecutionManager.StartTestRun(TestRunCriteria testRunCriteria, ITestRunEventsHandler eventHandler)
[10-Mar-18 11:08:51 Informational] ========== Run test finished: 0 run (0:01:00.0271437) ==========

Could we upgrade tests and only tests to latest .NET Core? Or at least starting from 2.0 as 1.1 is to way old and useless.

Add conversion from ExpressionInfo to Expression

Though ExpressionInfo provides lightweight alternative, there are tools where perf is not so critical (compile-time for instance) but they working with full Expression.

In order to support such tools and scenarios we may introduce conversion back to Expression.

Add extension method CompileFast for simple migration from Compile

Probably I need to do this from the start. Initially I was thinking that FEC targets specific area of performance optimization. Therefore you need to be aware exactly what you are doing, not just change to FEC and forget. Another part is that FEC have to prove its stability. Now I have this confidence..., until the new bug is found :)

Use lightweight replacement for Expression to improve performance of manually created expression

The final results

Benchmark of Creating and Compiling expression

() => new X(new A(), new B())

Comparing classic Expression

Expression.Lambda(Expression.New(_xCtor, Expression.New(_aCtor), Expression.New(_bCtor)))

vs lightweight ExpressionInfo:

ExpressionInfo.Lambda(ExpressionInfo.New(_xCtor, ExpressionInfo.New(_aCtor), ExpressionInfo.New(_bCtor)))

image

Supported lightweight ExpressionInfo's

  • ExpressionInfo.Lambda
  • ExpressionInfo.Parameter
  • ExpressionInfo.Constant
  • ExpressionInfo.Call
  • ExpressionInfo.Property
  • ExpressionInfo.Field

The rest is for the later.

Old info

Current results show that composing Expression Tree may take significant amount of time even compare to compilation of expression. It happens because Expression validates its child nodes and wrapped operations. But in case you do it on your own, that seems like a double work.

image

Introducing lightweight DryExpression may improve performance (and probably memory utilization) in such case. It may be also designed the way to include normal Expressions as children. So the implementation may start with sub-set of expressions, and DryExpressionCompiler may fallback to FastExpressionCompiler for the rest.

Current results for creating simple DryExpression.New vs Expression.New:

image

Why the result is incorrect?

I found your solution of optimizing LambdaExpression.Compile() from Google, and I've tried your latest version (1.5.0), which is really cool and fast, but...the result after running my application is weird.

I have NOT changed any logic of my code, just replaced "expression.Compile()" with "expression.CompileFast()" in my code and then ran it.

First one is the correct result (original result) that I expected.
correct

Second one is what "expression.CompileFast()" does, and the only matched number is the last number...why?
wrong

Constant Guid expression generates invalid IL

Any expression that contains ConstantExpression with Guid value will generate invalid IL:

$x.Id == .Constant<System.Guid>(cd0e7440-45dd-420a-a91d-67a99b67b26f)

The bigest problem is that the compilation does not fail and it won't fallback to normal .Compile(), but executing the produced delegate throws System.InvalidProgramException. (stacktrace's top element is the Closure) There is no way to reliably predict and handle this behaviour.

I couldn't debug into the produced func (as IL does not contain marked sequence points).

If you need the generated IL please tell me how to acquire it at runtime, thanks.

Further performance and memory allocation improvements

Ideas:

  1. Opt-in removal of 1st expression traversal round for collecting constants for closure.
    It is not neccessary if you know the lambda is pure. (#75)

  2. Optimize internal structures, especially related to closure and nested lambda support

  3. Some kind of caching. For what?

  4. add yours

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.