关于ZAKER 融媒体解决方案 合作 加入

为什么在 Expression.Call 上编译的 lambda 编译比应 .

CocoaChina 11-11

为什么在 Expression.Call 上编译的 lambda 构建比应该执行相同操作的委托慢一些?以及如何避免呢?

解释 BenchmarkDotNet 结果 .

我们正在比较 CallBuildedReal 和 CallLambda;其他两个 CallBuilded 和 CallLambdaConst 是 CallLambda 的 " 子表单 ", 并显示相等的数字 . 但是与 CallBuildedReal 的区别是很重要的 .

// [ Config ( typeof ( Config ) ) ] [ RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn ] [ ClrJob , CoreJob ] [ HtmlExporter, MarkdownExporter ] [ MemoryDiagnoser /*, InliningDiagnoser*/ ] public class BenchmarkCallSimple{ static Func<StringBuilder, int, int, bool> callLambda; static Func<StringBuilder, int, int, bool> callLambdaConst; static Func<StringBuilder, int, int, bool> callBuilded; static Func<StringBuilder, int, int, bool> callBuildedReal; private static bool Append<T> ( StringBuilder sb, T i1, T i2, Func<T, T, T> operation ) { sb.Append ( operation ( i1, i2 ) ) ; return true; } private static Func<StringBuilder, T, T, bool> BuildCallMethod<T> ( Func<T, T, T> operation ) { return ( sb, i1, i2 ) => { sb.Append ( operation ( i1, i2 ) ) ; return true; }; } private static int AddMethod ( int a, int b ) { return a + b; } static BenchmarkCallSimple ( ) { var x = Expression.Parameter ( typeof ( int ) ) ; var y = Expression.Parameter ( typeof ( int ) ) ; var additionExpr = Expression.Add ( x, y ) ; callLambdaConst = BuildCallMethod<int> ( AddMethod ) ; callLambda = BuildCallMethod<int> ( ( a, b ) => a + b ) ; var operationDelegate = Expression.Lambda<Func<int, int, int>> ( additionExpr, x, y ) .Compile ( ) ; callBuilded = BuildCallMethod ( operationDelegate ) ; var operationExpressionConst = Expression.Constant ( operationDelegate, operationDelegate.GetType ( ) ) ; var sb1 = Expression.Parameter ( typeof ( StringBuilder ) , "sb" ) ; var i1 = Expression.Parameter ( typeof ( int ) , "i1" ) ; var i2 = Expression.Parameter ( typeof ( int ) , "i2" ) ; var appendMethodInfo = typeof ( BenchmarkCallSimple ) .GetTypeInfo ( ) .GetDeclaredMethod ( nameof ( BenchmarkCallSimple.Append ) ) ; var appendMethodInfoGeneric = appendMethodInfo.MakeGenericMethod ( typeof ( int ) ) ; var appendCallExpression = Expression.Call ( appendMethodInfoGeneric, new Expression [ ] { sb1, i1, i2, operationExpressionConst } ) ; var appendLambda = Expression.Lambda ( appendCallExpression, new [ ] { sb1, i1, i2 } ) ; callBuildedReal = ( Func<StringBuilder, int, int, bool> ) ( appendLambda.Compile ( ) ) ; } [ Benchmark ] public string CallBuildedReal ( ) { StringBuilder sb = new StringBuilder ( ) ; var b = callBuildedReal ( sb, 1, 2 ) ; return sb.ToString ( ) ; } [ Benchmark ] public string CallBuilded ( ) { StringBuilder sb = new StringBuilder ( ) ; var b = callBuilded ( sb, 1, 2 ) ; return sb.ToString ( ) ; } [ Benchmark ] public string CallLambda ( ) { StringBuilder sb = new StringBuilder ( ) ; var b = callLambda ( sb, 1, 2 ) ; return sb.ToString ( ) ; } [ Benchmark ] public string CallLambdaConst ( ) { StringBuilder sb = new StringBuilder ( ) ; var b = callLambdaConst ( sb, 1, 2 ) ; return sb.ToString ( ) ; }}

结果:

BenchmarkDotNet=v0.10.5, OS=Windows 10.0.14393Processor=Intel Core i5-2500K CPU 3.30GHz ( Sandy Bridge ) , ProcessorCount=4Frequency=3233539 Hz, Resolution=309.2587 ns, Timer=TSC [ Host ] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1648.0 Clr : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1648.0 Core : .NET Core 4.6.25009.03, 64bit RyuJIT Method | Job | Runtime | Mean | Error | StdDev | Min | Max | Median | Rank | Gen 0 | Allocated |---------------- |----- |-------- |---------:|---------:|---------:|---------:|---------:|---------:|-----:|-------:|----------:| CallBuildedReal | Clr | Clr | 137.8 ns | 2.903 ns | 4.255 ns | 133.6 ns | 149.6 ns | 135.6 ns | 7 | 0.0580 | 192 B | CallBuilded | Clr | Clr | 122.7 ns | 2.068 ns | 1.934 ns | 118.5 ns | 126.2 ns | 122.6 ns | 6 | 0.0576 | 192 B | CallLambda | Clr | Clr | 119.8 ns | 1.342 ns | 1.255 ns | 117.9 ns | 121.7 ns | 119.6 ns | 5 | 0.0576 | 192 B | CallLambdaConst | Clr | Clr | 121.7 ns | 1.347 ns | 1.194 ns | 120.1 ns | 124.5 ns | 121.6 ns | 6 | 0.0571 | 192 B | CallBuildedReal | Core | Core | 114.8 ns | 2.263 ns | 2.117 ns | 112.7 ns | 118.8 ns | 113.7 ns | 3 | 0.0594 | 191 B | CallBuilded | Core | Core | 109.0 ns | 1.701 ns | 1.591 ns | 106.5 ns | 112.2 ns | 108.8 ns | 2 | 0.0599 | 191 B | CallLambda | Core | Core | 107.0 ns | 1.181 ns | 1.105 ns | 105.7 ns | 109.4 ns | 106.8 ns | 1 | 0.0593 | 191 B | CallLambdaConst | Core | Core | 117.3 ns | 2.706 ns | 3.704 ns | 113.4 ns | 127.8 ns | 116.0 ns | 4 | 0.0592 | 191 B |

基准代码:

注意 1:有类似的 SO 线程 " Performance of expression trees", 其中构建表达式在基准测试中显示出最佳结果 .

注意 2:当我将获得编译表达式的 IL 代码时 , 我应该接近回答 , 所以我正在尝试学习如何获取编译表达式的 IL 代码 ( linqpad ?、 ilasm 集成到 VS ?、动态汇编? ) , 但是 , 如果您知道可以通过 VS 实现的简单插件 - 会对我有很大帮助 .

注意 3:这不起作用

var assemblyBuilder = System.AppDomain.CurrentDomain.DefineDynamicAssembly ( new AssemblyName ( "testLambda" ) ,System.Reflection.Emit.AssemblyBuilderAccess.Save ) ; var modelBuilder = assemblyBuilder.DefineDynamicModule ( "testLambda_module", "testLambda.dll" ) ; var typeBuilder = modelBuilder.DefineType ( "testLambda_type" ) ; var method = typeBuilder.DefineMethod ( "testLambda_method", MethodAttributes.Public | MethodAttributes.Static, typeof ( bool ) , new [ ] { typeof ( StringBuilder ) , typeof ( int ) , typeof ( int ) , typeof ( bool ) } ) ; appendLambda.CompileToMethod ( method ) ; typeBuilder.CreateType ( ) ; assemblyBuilder.Save ( "testLambda.dll" ) ;

由于 System.TypeInitializationException:" InvalidOperationException:CompileToMethod 无法编译常量’ System.Func3 [ System.Int32,System.Int32,System.Int32 ] ", 因为它是一个非平凡的值 , 例如活动对象 . 可以构造该值的表达式树 ."

这意味着 appendLambda` 包含参数类型为 Func 的参数 , 该参数类型不是基本类型 , 并且 CompileToMethod 仅使用基本类型是有限制的 .

由于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 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.

有诸如FastExpressionCompiler之类的工具可以帮助缓解问题 ( 免责声明:我是作者 )

更新:查看已编译委托的 IL

> 可以将编译后的委托 IL 作为字节数组获取:

var hello = "Hello";Expression<Func<string>> getGreetingExpr = ( ) => hello + " me";var getGreeting = getGreetingExpr.Compile ( ) ;var methodBody = getGreeting.Method.GetMethodBody ( ) ;var ilBytes = methodBody.GetILAsByteArray ( ) ;

> 您需要一种解析 / 读取数组并将其转换为 IL 指令和参数的方法 .

可惜 , 但我没有找到允许我这样做的工具或强大的 NuGet 软件包 :- (

这是相关的SO question.

最接近的工具可能是this.

以上内容由"CocoaChina"上传发布 查看原文
相关标签 windows

觉得文章不错,微信扫描分享好友

扫码分享