zl程序教程

您现在的位置是:首页 >  大数据

当前栏目

关于Expression Tree和IL Emit的所谓的"性能差别"

性能 关于 quot Tree 差别 expression Emit 所谓
2023-09-27 14:27:56 时间

昨天写了《三种属性操作性能比较》,有个网友写信问我一个问题:从性能上看,Expression Tree和IL Emit孰优孰劣?虽然我在回信中作了简单的回答,但不知道这个网友是否懂我的意思。反正今天呆在家里也没事儿,干脆再就这个话题再写一篇文章。

目录:
一、Expression Tree和IL Emit并不存在所谓的性能差异
二、属性赋值操作的两种写法
三、属性取值操作的两种写法
四、两种写法对应的IL

 

一、Expression Tree和IL Emit并不存在所谓的性能差异

Expression Tree和IL Emit的性能孰优孰劣,这本是个“不是问题的问题”。因为两者之间并不存在本质的区别,所以也谈不上性能的优劣问题。举个例子来说,我们知道.NET Framework 2.0,3.0和3.5使用的是相同的CLR。但是C# 3.0、3.5在2.0的基础上推出了很多语言层面的特性,比如自动实现属性:

 1: public class Foo

我们也可以按照下面“传统”的方式来写上面这段代码,谁都知道这两种写法在本质上是完全一样的。就上面的程序来说,在编译的时候C#编译器会将其转化成下一种形式,什么自动实现属性、匿名属性、扩展方法,都是浮云——语法糖而已。


Expression Tree和IL Emit之间的关系与这些“语法糖”类似。编译后的Expression Tree就是IL代码;而IL Emit让我们可以用高级语言的编程方式来控制中间语言(IL)程序。由于最终的东西都是一样的,谈不上谁比谁好的问题。编译Expression Tree实现了向IL的转换,如果你通过IL Emit写的IL能够比Expression Tree自动转换的好,那么你的程序性能就好,否则性能就差。但是我们不能说Expression Tree和IL Emit在性能上孰优孰劣。

二、属性赋值操作的两种写法

我们说明Expression Tree和IL Emit之间不存在性能的差异,我们不妨写个例子。简单起见,我们还是采用前面谈到过的属性赋值和取值的操作为例。假设有如下一个接口IFoo,包含一个类型和名称均为Bar的可读写的属性。


现在我们通过Expression Tree和IL Emit两种方式编写一个静态方法对IFoo对象的Bar属性进行赋值。简单起见,我们甚至将静态方法的参数类型直接指定为IFoo和Bar,从而省去了类型转换操作。下面是通过Expression Tree进行属性赋值的方法:SetPropertyValueViaExpression。


 6: var setPropertyValue = Expression.Call(target, property.GetSetMethod(), propertyValue);

 7: var setAction= Expression.Lambda Action IFoo, Bar (setPropertyValue, target, propertyValue).Compile();

 4: DynamicMethod method = new DynamicMethod("SetValue", null, new Type[] { typeof(IFoo), typeof(Bar) });

 13: var setAction = (Action IFoo, Bar )method.CreateDelegate(typeof(Action IFoo, Bar 


三、属性取值操作的两种写法

接下来,我们来编写用于进行属性取值操作的方法。下面的SetPropertyValueViaExpression方法是基于Expression Tree的。


 6: var getFunc = Expression.Lambda Func IFoo, Bar (getPropertyValue, target).Compile();

 4: DynamicMethod method = new DynamicMethod("GetValue", typeof(Bar), new Type[] { typeof(IFoo) });

 12: var getFunc = (Func IFoo, Bar )method.CreateDelegate(typeof(Func IFoo, Bar 


四、看看两种写法对应的IL

我们说过,经过编译的Expression Tree就是一段IL代码,而IL Emit则直接反映了IL的执行流程。要判断两者在性能方面孰优孰劣,我们只需要看看Expression Tree最终被转换成怎样的IL。我们现在的做法是动态生成一个程序集,将Expression Tree部分定义到一个方法之中。虽然IL Emit已经是真实底反映了底层的IL代码,但是为了我们的比较更加直观,我们也将IL Emit的部分也写入相应的方法。

为此我们在一个Console应用中的Main方法编写了如下的代码:动态创建了名称为Artech.EmitVsExpression的程序集,其中定义了同名的模块。一个唯一的类型Program定义其中,其中定义了四个静态方法:GetPropertyValueViaExpression、SetPropertyValueViaExpression、GetPropertyValueViaEmit和GetPropertyValueViaEmit。而方法体部分则是上面Expression Tree和IL Emit定义的内容。最后这个程序集被保存为一个同名的.dll文件。


 4: var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Artech.EmitVsExpression"), AssemblyBuilderAccess.RunAndSave);

 5: var moduleBuilder = assemblyBuilder.DefineDynamicModule("Artech.EmitVsExpression", "Artech.EmitVsExpression.dll");

 9: var methodBuilder = typeBuilder.DefineMethod("GetPropertyValueViaExpression", MethodAttributes.Static | MethodAttributes.Public, typeof(Bar), new Type[] { typeof(IFoo) });

 12: Expression.Lambda Func IFoo, Bar (getPropertyValue, target).CompileToMethod(methodBuilder);

 15: methodBuilder = typeBuilder.DefineMethod("SetPropertyValueViaExpression", MethodAttributes.Static | MethodAttributes.Public, typeof(void), new Type[] { typeof(IFoo), typeof(Bar) });

 18: var setPropertyValue = Expression.Call(target, property.GetSetMethod(), propertyValue);

 19: Expression.Lambda Action IFoo, Bar (setPropertyValue, target, propertyValue).CompileToMethod(methodBuilder);

 22: methodBuilder = typeBuilder.DefineMethod("GetPropertyValueViaEmit", MethodAttributes.Static| MethodAttributes.Public, typeof(Bar), new Type[] { typeof(IFoo) });

 25: ilGenerator.EmitCall(OpCodes.Callvirt, property.GetGetMethod(), null);

 29: methodBuilder = typeBuilder.DefineMethod("SetPropertyValueViaEmit", MethodAttributes.Static | MethodAttributes.Public, typeof(void), new Type[] { typeof(IFoo), typeof(Bar) });

 33: ilGenerator.EmitCall(OpCodes.Callvirt, property.GetSetMethod(), null);

现在我们通过IL Disassembler打开这个.dll文件,看看四个静态方法的IL代码。下面是用于用于获取属性值的GetPropertyValueViaExpression和GetPropertyValueViaEmit方法,我们可以看出它们具有完全一致的方式体。


 2: GetPropertyValueViaExpression(class [EmitVsExpressionTree]IFoo A_0) cil managed

 7: IL_0001: callvirt instance class [EmitVsExpressionTree]Bar [EmitVsExpressionTree]IFoo::get_Bar()

 12: GetPropertyValueViaEmit(class [EmitVsExpressionTree]IFoo A_0) cil managed

 17: IL_0001: callvirt instance class [EmitVsExpressionTree]Bar [EmitVsExpressionTree]IFoo::get_Bar()

下面是用于对属性进行赋值的两个静态方法:SetPropertyValueViaExpression和SetPropertyValueViaEmit,毫无疑问它们之间也没有差异。到现在,你还在怀疑两种之间在性能上孰优孰劣吗?


 1: .method public static void SetPropertyValueViaExpression(class [EmitVsExpressionTree]IFoo A_0,

 8: IL_0002: callvirt instance void [EmitVsExpressionTree]IFoo::set_Bar(class [EmitVsExpressionTree]Bar)

 12: .method public static void SetPropertyValueViaEmit(class [EmitVsExpressionTree]IFoo A_0,

 19: IL_0002: callvirt instance void [EmitVsExpressionTree]IFoo::set_Bar(class [EmitVsExpressionTree]Bar)

既然在IL上它们没有差别,那么它们就是两对等效的方法。如果你通过Reflector来打开我们生成的.dll,你会清晰地看到这真的是两对完全一致的方法。


晚绑定场景下对象属性赋值和取值可以不需要PropertyInfo
三种属性操作性能比较:PropertyInfo + Expression Tree + Delegate.CreateDelegate
关于Expression Tree和IL Emit的所谓的"性能差别"

作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 原文链接

一个简单的例子让你轻松地明白JavaScript中apply、call、bind三者的用法及区别 这篇文章也算是讲解了前端面试 常考的知识点 ,即关于JavaScript中apply、call、bind三者的用法及区别。 如果有些小伙伴已经对该知识有一定的了解了,可以直接跳到最后看 总结
重构——38以卫语句取代嵌套条件表达式(Replace Nested Conditional with Guard Clause) 以卫语句取代嵌套条件表达式(Replace Nested Conditional with Guard Clause):函数中的条件逻辑使人难以看清正常的执行路径;使用卫语句表现所有特殊情况
重构——26以字面常量取代魔法数(Replace Magic Number with Symbolic Constant) 以字面常量取代魔法数(Replace Magic Number with Symbolic Constant):你有一个字面数值,带有特别含义:创造一个常量,根据其意义为它命名,并将上述的字面数值替换为这个常量
[译]Functor 与 Category (软件编写)(第六部分) 本文讲的是[译]Functor 与 Category (软件编写)(第六部分),所谓 functor(函子),是能够对其进行 map 操作的对象。换言之,functor 可以被认为是一个容器,该容器容纳了一个值,并且暴露了一个接口(译注:即 map 接口),该接口使得外界的函数能够获取容器中的值。