关于Expression Tree和IL Emit的所谓的"性能差别"
2011-03-27 15:30
435 查看
昨天写了《三种属性操作性能比较》,有个网友写信问我一个问题:从性能上看,Expression Tree和IL Emit孰优孰劣?虽然我在回信中作了简单的回答,但不知道这个网友是否懂我的意思。反正今天呆在家里也没事儿,干脆再就这个话题再写一篇文章。
[/code]
我们也可以按照下面“传统”的方式来写上面这段代码,谁都知道这两种写法在本质上是完全一样的。就上面的程序来说,在编译的时候C#编译器会将其转化成下一种形式,什么自动实现属性、匿名属性、扩展方法,都是浮云——语法糖而已。
[/code]
Expression Tree和IL Emit之间的关系与这些“语法糖”类似。编译后的Expression Tree就是IL代码;而IL Emit让我们可以用高级语言的编程方式来控制中间语言(IL)程序。由于最终的东西都是一样的,谈不上谁比谁好的问题。编译Expression Tree实现了向IL的转换,如果你通过IL Emit写的IL能够比Expression Tree自动转换的好,那么你的程序性能就好,否则性能就差。但是我们不能说Expression Tree和IL Emit在性能上孰优孰劣。
[/code]
现在我们通过Expression Tree和IL Emit两种方式编写一个静态方法对IFoo对象的Bar属性进行赋值。简单起见,我们甚至将静态方法的参数类型直接指定为IFoo和Bar,从而省去了类型转换操作。下面是通过Expression Tree进行属性赋值的方法:SetPropertyValueViaExpression。
[/code]
而下面的SetPropertyValueViaEmit则通过IL Emit的方式完成了一样的工作:
[/code]
[/code]
下面则是基于IL Emit的版本:
[/code]
为此我们在一个Console应用中的Main方法编写了如下的代码:动态创建了名称为Artech.EmitVsExpression的程序集,其中定义了同名的模块。一个唯一的类型Program定义其中,其中定义了四个静态方法:GetPropertyValueViaExpression、SetPropertyValueViaExpression、GetPropertyValueViaEmit和GetPropertyValueViaEmit。而方法体部分则是上面Expression Tree和IL Emit定义的内容。最后这个程序集被保存为一个同名的.dll文件。
[/code]
现在我们通过IL Disassembler打开这个.dll文件,看看四个静态方法的IL代码。下面是用于用于获取属性值的GetPropertyValueViaExpression和GetPropertyValueViaEmit方法,我们可以看出它们具有完全一致的方式体。
[/code]
下面是用于对属性进行赋值的两个静态方法:SetPropertyValueViaExpression和SetPropertyValueViaEmit,毫无疑问它们之间也没有差异。到现在,你还在怀疑两种之间在性能上孰优孰劣吗?
[/code]
既然在IL上它们没有差别,那么它们就是两对等效的方法。如果你通过Reflector来打开我们生成的.dll,你会清晰地看到这真的是两对完全一致的方法。
[/code]
晚绑定场景下对象属性赋值和取值可以不需要PropertyInfo
三种属性操作性能比较:PropertyInfo + Expression Tree + Delegate.CreateDelegate
关于Expression Tree和IL Emit的所谓的"性能差别"
目录:
一、Expression Tree和IL Emit并不存在所谓的性能差异
二、属性赋值操作的两种写法
三、属性取值操作的两种写法
四、两种写法对应的IL
一、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的基础上推出了很多语言层面的特性,比如自动实现属性:[code] public class Foo { public Bar Bar{get;set;} public Foo() { this.Bar = new Bar(); } }
[/code]
我们也可以按照下面“传统”的方式来写上面这段代码,谁都知道这两种写法在本质上是完全一样的。就上面的程序来说,在编译的时候C#编译器会将其转化成下一种形式,什么自动实现属性、匿名属性、扩展方法,都是浮云——语法糖而已。
[code] public class Foo { private Bar _bar; public Bar Bar { get{return _bar;} set{_bar = value;} } public Foo() { _bar = new Bar(); } }
[/code]
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的可读写的属性。[code] public interface IFoo { Bar{get;set;} } public class Bar{}
[/code]
现在我们通过Expression Tree和IL Emit两种方式编写一个静态方法对IFoo对象的Bar属性进行赋值。简单起见,我们甚至将静态方法的参数类型直接指定为IFoo和Bar,从而省去了类型转换操作。下面是通过Expression Tree进行属性赋值的方法:SetPropertyValueViaExpression。
[code] public static void SetPropertyValueViaExpression(IFoo foo, Bar bar) { var property = typeof(IFoo).GetProperty("Bar"); var target = Expression.Parameter(typeof(IFoo)); var propertyValue = Expression.Parameter(typeof(Bar)); var setPropertyValue = Expression.Call(target, property.GetSetMethod(), propertyValue); var setAction= Expression.Lambda<Action<IFoo, Bar>>(setPropertyValue, target, propertyValue).Compile(); setAction(foo, bar); }
[/code]
而下面的SetPropertyValueViaEmit则通过IL Emit的方式完成了一样的工作:
[code] public static void SetPropertyValueViaEmit(IFoo foo, Bar bar) { var property = typeof(IFoo).GetProperty("Bar"); DynamicMethod method = new DynamicMethod("SetValue", null, new Type[] { typeof(IFoo), typeof(Bar) }); ILGenerator ilGenerator = method.GetILGenerator(); ilGenerator.Emit(OpCodes.Ldarg_0); ilGenerator.Emit(OpCodes.Ldarg_1); ilGenerator.EmitCall(OpCodes.Callvirt, property.GetSetMethod(), null); ilGenerator.Emit(OpCodes.Ret); method.DefineParameter(1, ParameterAttributes.In, "obj"); method.DefineParameter(2, ParameterAttributes.In, "value"); var setAction = (Action<IFoo, Bar>)method.CreateDelegate(typeof(Action<IFoo, Bar>)); setAction(foo, bar); }
[/code]
三、属性取值操作的两种写法
接下来,我们来编写用于进行属性取值操作的方法。下面的SetPropertyValueViaExpression方法是基于Expression Tree的。[code] public static Bar GetPropertyValueViaExpression(IFoo foo) { var property = typeof(IFoo).GetProperty("Bar"); var target = Expression.Parameter(typeof(IFoo)); var getPropertyValue = Expression.Property(target, property); var getFunc = Expression.Lambda<Func<IFoo, Bar>>(getPropertyValue, target).Compile(); return getFunc(foo); }
[/code]
下面则是基于IL Emit的版本:
[code] public static Bar GetPropertyValueViaEmit(IFoo foo) { var property = typeof(IFoo).GetProperty("Bar"); DynamicMethod method = new DynamicMethod("GetValue", typeof(Bar), new Type[] { typeof(IFoo) }); ILGenerator ilGenerator = method.GetILGenerator(); ilGenerator.Emit(OpCodes.Ldarg_0); ilGenerator.EmitCall(OpCodes.Callvirt, property.GetGetMethod(), null); ilGenerator.Emit(OpCodes.Ret); method.DefineParameter(1, ParameterAttributes.In, "target"); var getFunc = (Func<IFoo, Bar>)method.CreateDelegate(typeof(Func<IFoo, Bar>)); return getFunc(foo); }
[/code]
四、看看两种写法对应的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文件。
[code] static void Main() { var property = typeof(IFoo).GetProperty("Bar"); var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Artech.EmitVsExpression"), AssemblyBuilderAccess.RunAndSave); var moduleBuilder = assemblyBuilder.DefineDynamicModule("Artech.EmitVsExpression", "Artech.EmitVsExpression.dll"); var typeBuilder = moduleBuilder.DefineType("Program"); //GetPropertyValueViaExpression var methodBuilder = typeBuilder.DefineMethod("GetPropertyValueViaExpression", MethodAttributes.Static | MethodAttributes.Public, typeof(Bar), new Type[] { typeof(IFoo) }); var target = Expression.Parameter(typeof(IFoo)); var getPropertyValue = Expression.Property(target, property); Expression.Lambda<Func<IFoo, Bar>>(getPropertyValue, target).CompileToMethod(methodBuilder); //SetPropertyValueViaExpression methodBuilder = typeBuilder.DefineMethod("SetPropertyValueViaExpression", MethodAttributes.Static | MethodAttributes.Public, typeof(void), new Type[] { typeof(IFoo), typeof(Bar) }); target = Expression.Parameter(typeof(IFoo)); var propertyValue = Expression.Parameter(typeof(Bar)); var setPropertyValue = Expression.Call(target, property.GetSetMethod(), propertyValue); Expression.Lambda<Action<IFoo, Bar>>(setPropertyValue, target, propertyValue).CompileToMethod(methodBuilder); //GetPropertyValueViaEmit methodBuilder = typeBuilder.DefineMethod("GetPropertyValueViaEmit", MethodAttributes.Static| MethodAttributes.Public, typeof(Bar), new Type[] { typeof(IFoo) }); ILGenerator ilGenerator = methodBuilder.GetILGenerator(); ilGenerator.Emit(OpCodes.Ldarg_0); ilGenerator.EmitCall(OpCodes.Callvirt, property.GetGetMethod(), null); ilGenerator.Emit(OpCodes.Ret); //SetPropertyValueViaEmit methodBuilder = typeBuilder.DefineMethod("SetPropertyValueViaEmit", MethodAttributes.Static | MethodAttributes.Public, typeof(void), new Type[] { typeof(IFoo), typeof(Bar) }); ilGenerator = methodBuilder.GetILGenerator(); ilGenerator.Emit(OpCodes.Ldarg_0); ilGenerator.Emit(OpCodes.Ldarg_1); ilGenerator.EmitCall(OpCodes.Callvirt, property.GetSetMethod(), null); ilGenerator.Emit(OpCodes.Ret); typeBuilder.CreateType(); assemblyBuilder.Save("Artech.EmitVsExpression.dll"); }
[/code]
现在我们通过IL Disassembler打开这个.dll文件,看看四个静态方法的IL代码。下面是用于用于获取属性值的GetPropertyValueViaExpression和GetPropertyValueViaEmit方法,我们可以看出它们具有完全一致的方式体。
[code] .method public static class [EmitVsExpressionTree]Bar GetPropertyValueViaExpression(class [EmitVsExpressionTree]IFoo A_0) cil managed { // Code size 7 (0x7) .maxstack 1 IL_0000: ldarg.0 IL_0001: callvirt instance class [EmitVsExpressionTree]Bar [EmitVsExpressionTree]IFoo::get_Bar() IL_0006: ret } // end of method Program::GetPropertyValueViaExpression .method public static class [EmitVsExpressionTree]Bar GetPropertyValueViaEmit(class [EmitVsExpressionTree]IFoo A_0) cil managed { // Code size 7 (0x7) .maxstack 1 IL_0000: ldarg.0 IL_0001: callvirt instance class [EmitVsExpressionTree]Bar [EmitVsExpressionTree]IFoo::get_Bar() IL_0006: ret } // end of method Program::GetPropertyValueViaEmit
[/code]
下面是用于对属性进行赋值的两个静态方法:SetPropertyValueViaExpression和SetPropertyValueViaEmit,毫无疑问它们之间也没有差异。到现在,你还在怀疑两种之间在性能上孰优孰劣吗?
[code] .method public static void SetPropertyValueViaExpression(class [EmitVsExpressionTree]IFoo A_0, class [EmitVsExpressionTree]Bar A_1) cil managed { // Code size 8 (0x8) .maxstack 2 IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: callvirt instance void [EmitVsExpressionTree]IFoo::set_Bar(class [EmitVsExpressionTree]Bar) IL_0007: ret } // end of method Program::SetPropertyValueViaExpression .method public static void SetPropertyValueViaEmit(class [EmitVsExpressionTree]IFoo A_0, class [EmitVsExpressionTree]Bar A_1) cil managed { // Code size 8 (0x8) .maxstack 2 IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: callvirt instance void [EmitVsExpressionTree]IFoo::set_Bar(class [EmitVsExpressionTree]Bar) IL_0007: ret } // end of method Program::SetPropertyValueViaEmit
[/code]
既然在IL上它们没有差别,那么它们就是两对等效的方法。如果你通过Reflector来打开我们生成的.dll,你会清晰地看到这真的是两对完全一致的方法。
[code] internal class Program { // Methods public static Bar GetPropertyValueViaEmit(IFoo foo1) { return foo1.Bar; } public static Bar GetPropertyValueViaExpression(IFoo foo1) { return foo1.Bar; } public static void SetPropertyValueViaEmit(IFoo foo1, Bar bar1) { foo1.Bar = bar1; } public static void SetPropertyValueViaExpression(IFoo foo1, Bar bar1) { foo1.Bar = bar1; } }
[/code]
晚绑定场景下对象属性赋值和取值可以不需要PropertyInfo
三种属性操作性能比较:PropertyInfo + Expression Tree + Delegate.CreateDelegate
关于Expression Tree和IL Emit的所谓的"性能差别"
相关文章推荐
- 关于Expression Tree和IL Emit的所谓的"性能差别"“.NET研究”;
- 一起谈.NET技术,关于Expression Tree和IL Emit的所谓的"性能差别"
- "Entity Framework数据插入性能追踪"读后总结
- jmeter性能测试,基于scf框架的"java请求"接口封装、环境配置与测试
- "Entity Framework数据插入性能追踪"读后总结
- 不存在所谓的"JSON对象"!
- MVC详解:了解真正所谓的"框架"
- MVC详解:了解真正所谓的"框架"
- 博客自动变新样子了,这就是所谓"新皮肤"?
- 打印出所有的 "水仙花数 ",所谓 "水仙花数 "是指一个三位数,其各位数字立方和等于该数本身。例如:153是一个 "水仙花数 ",因为153=1的三次方+5的三次方+3的三次方。
- 初学java:打印出1000以内的 "水仙花数 ",所谓 "水仙花数 "是指一个三位数,其各位数字立方和等于该数本身。
- 海奥华预言--第九章 我们所谓的"文明"
- "Android 性能优化"-Android面试必问"精华技能点"汇总
- "菜鸟"与大神程序员之间的差别(转载)
- 用js实现下列功能,将给定的数字转化为千分位的格式,如把"10000"转化为"10,000",并考虑性能方面的因素
- 打印出所有的 "水仙花数",所谓 "水仙花数 " 是指一个三位数, 其各位数字立方和等于该数本身。例如: 153是一个 "水仙花数 ", 因为153=1的三次方+5的三次方+3的三次方。
- 【Java性能】你需要知道的:Java字符串连接使用"+"和StringBuilder性能比较
- 题目:打印出所有的 "水仙花数 ",所谓 "水仙花数 "是指一个三位数,其各位数字立方和等于该数本身。例如:153是一个 "水仙花 数 ",因为153=1的三次方+5的三次方+3的三次方。
- 打印出所有的 "水仙花数 ",所谓 "水仙花数 "是指一个三位数,其各位数字立方和等于该数本身
- Long.ValueOf("str") 和Long.parseLong("str")真的有性能差别吗?