Coder Social home page Coder Social logo

fastexpressioncompiler's Introduction

FastExpressionCompiler

NuGet Badge license

Windows build Linux build

Supported platforms: .NET 4.5+, .NET Standard 1.3, .NET Standard 2.0

Why

Expression tree compilation is used by wide range of tools, e.g. IoC/DI containers, Serializers, OO Mappers. But the performance of compilation with Expression.Compile() is just slow. Moreover, the compiled delegate may be slower than manually created delegate because of the reasons:

TL;DR;

The question is, why is the compiled delegate way slower than a manually-written delegate? Expression.Compile creates a DynamicMethod and associates it with an anonymous assembly to run it in a sandboxed environment. This makes it safe for a dynamic method to be emitted and executed by partially trusted code but adds some run-time overhead.

Fast Expression Compiler is ~20 times faster than Expression.Compile().
The compiled delegate may be in some cases??? ~15 times faster than the one produced by Expression.Compile().

Benchmarks

BenchmarkDotNet=v0.10.3.0, OS=Microsoft Windows 10.0.14393
Processor=Intel(R) Core(TM) i5-6300U CPU 2.40GHz, ProcessorCount=4
Frequency=2437493 Hz, Resolution=410.2576 ns, Timer=TSC
dotnet cli version=1.0.0-preview2-1-003177
  [Host]     : .NET Core 4.6.24628.01, 64bit RyuJIT
  DefaultJob : .NET Core 4.6.24628.01, 64bit RyuJIT

Hoisted expression with 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 StdDev Scaled Scaled-StdDev Gen 0 Gen 1 Allocated
ExpressionCompile 426.1452 us 10.8108 us 29.91 1.15 - - 4.36 kB
ExpressionCompileFast 14.2593 us 0.4461 us 1.00 0.00 1.0579 0.2035 2.72 kB

Invoking compiled delegate comparing to direct constructor call:

Method Mean StdErr StdDev Scaled Scaled-StdDev Gen 0 Allocated
DirectConstructorCall 9.7089 ns 0.1423 ns 0.5692 ns 0.70 0.04 0.0202 32 B
CompiledLambda 15.8753 ns 0.2077 ns 1.2113 ns 1.15 0.09 0.0198 32 B
FastCompiledLambda 13.8102 ns 0.0963 ns 0.3473 ns 1.00 0.00 0.0195 32 B

Hoisted expression with 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 StdDev Scaled Scaled-StdDev Gen 0 Gen 1 Allocated
ExpressionCompile 885.8788 us 14.5933 us 17.07 0.37 - - 12.28 kB
ExpressionFastCompile 51.9052 us 0.7952 us 1.00 0.00 4.0616 1.3761 8 kB

Invoking compiled delegate comparing to direct method call:

Method Mean StdDev Scaled Scaled-StdDev Gen 0 Allocated
DirectMethodCall 166.9818 ns 3.0175 ns 0.86 0.02 0.1111 184 B
CompiledLambda 2,547.4770 ns 46.7880 ns 13.08 0.27 0.0900 280 B
FastCompiledLambda 194.8093 ns 2.2769 ns 1.00 0.00 0.1399 240 B

Manually composed expression with parameters and closure

    var a = new A();
    var bParamExpr = Expression.Parameter(typeof(B), "b");

    var expr = Expression.Lambda(
        Expression.New(typeof(X).GetTypeInfo().DeclaredConstructors.First(),
            Expression.Constant(a, typeof(A)), bParamExpr),
        bParamExpr);

Compiling expression:

Method Mean StdDev Median Scaled Scaled-StdDev Gen 0 Gen 1 Allocated
CompileExpression 397.5570 us 27.6319 us 386.6312 us 27.46 1.92 - - 4.72 kB
CompileFastExpression 14.4785 us 0.1752 us 14.5392 us 1.00 0.00 1.3086 0.5762 2.26 kB

Invoking compiled delegate comparing to normal delegate:

Method Mean StdErr StdDev Scaled Scaled-StdDev Gen 0 Allocated
RawLambda 12.5377 ns 0.0839 ns 0.3249 ns 0.93 0.03 0.0196 32 B
CompiledLambda 14.2297 ns 0.1640 ns 0.6135 ns 1.06 0.05 0.0195 32 B
FastCompiledLambda 13.4183 ns 0.0352 ns 0.1317 ns 1.00 0.00 0.0195 32 B

Manually composed complex ExpressionInfo

FastExpressionCompiler.ExpressionInfo is the lightweight version of Expression. You may consider it instead of Expression when you are validating the Expression arguments on your own, and not relying on Expression composition exceptions.

Note: Explore ExpressionInfo class in FastExpressionCompiler.cs for finding the supported expression types.

Method Mean StdDev Scaled Scaled-StdDev Gen 0 Gen 1 Allocated
CreateExpression_and_Compile 632.4135 us 26.5744 us 32.37 1.72 - - 7.81 kB
CreateExpression_and_CompileFast 29.7829 us 1.6242 us 1.52 0.10 1.8694 0.6487 3.86 kB
CreateExpressionInfo_and_CompileFast 19.5569 us 0.6645 us 1.00 0.00 1.4689 0.3703 3.38 kB

Usage

Hoisted lambda expression (created by 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(typeof(X).GetTypeInfo().DeclaredConstructors.First(),
            Expression.Constant(a, typeof(A)), bParamExpr),
        bParamExpr);

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

Status

Initially developed and currently used in DryIoc.

Used in Marten v2, ExpressionToCodeLib.

What is supported:

  • Manually created or hoisted lambda expressions with closure
  • Nested lambdas
  • Constructor and method calls, lambda invocation
  • Property and member access
  • Equality and ?: operators

What is not supported:

  • ??, ?., bitwise, and ariphmetic operators
  • Expression.Block and declarative expressions added in .NET 4.0
  • Check an open issues for more details

Note: The current limitations may be lifted by wrapping not yet supported expression into method or property.

How

The idea is to provide fast compilation of selected/supported expression types, and fall back to normal Expression.Compile() for the not (yet) supported types.

Compilation is done by visiting expression nodes and emitting the IL. The supporting code preserved as minimalistic as possible for perf.

Expression is visited in two rounds:

  1. To collect constants and nested lambdas into closure objects.
  2. To emit an IL.

If some processing round visits a not supported expression node, then compilation is aborted, and null is returned enabling the fallback to normal Expression.Compile().

fastexpressioncompiler's People

Contributors

dadhi avatar iarovyi avatar z4kn4fein avatar eamonnerbonne avatar

Watchers

Camilo E. Hidalgo Estevez avatar James Cloos avatar

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.