博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
关于Expression Tree和IL Emit的所谓的"性能差别"
阅读量:4537 次
发布时间:2019-06-08

本文共 9835 字,大约阅读时间需要 32 分钟。

昨天写了《》,有个网友写信问我一个问题:从性能上看,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
2: {
3:     public Bar Bar{get;set;}
4:     public Foo()
5:     {
6:         this.Bar = new Bar();
7:     }
8: }

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

1: public class Foo
2: {
3:     private Bar _bar;
4:     public Bar Bar
5:     {
6:         get{
return _bar;}
7:         set{_bar = value;}
8:     }
9:     public Foo()
10:     {
11:         _bar = new Bar();
12:     }
13: }

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的可读写的属性。

1: public interface IFoo
2: {
3:     Bar{get;set;}
4: }
5: public class Bar{}

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

1: public static void SetPropertyValueViaExpression(IFoo foo, Bar bar)
2: {
3:     var property = typeof(IFoo).GetProperty("Bar");
4:     var target = Expression.Parameter(typeof(IFoo));
5:     var propertyValue = Expression.Parameter(typeof(Bar));
6:     var setPropertyValue = Expression.Call(target, property.GetSetMethod(), propertyValue);
7:     var setAction= Expression.Lambda
>(setPropertyValue, target, propertyValue).Compile();
8:     setAction(foo, bar);
9: }

而下面的SetPropertyValueViaEmit则通过IL Emit的方式完成了一样的工作:

1: public static void SetPropertyValueViaEmit(IFoo foo, Bar bar)
2: {
3:     var property = typeof(IFoo).GetProperty("Bar");
4:     DynamicMethod method = new DynamicMethod("SetValue", null, new Type[] { typeof(IFoo), typeof(Bar) });
5:     ILGenerator ilGenerator = method.GetILGenerator();
6:     ilGenerator.Emit(OpCodes.Ldarg_0);
7:     ilGenerator.Emit(OpCodes.Ldarg_1);
8:     ilGenerator.EmitCall(OpCodes.Callvirt, property.GetSetMethod(), null);
9:     ilGenerator.Emit(OpCodes.Ret);
10: 
11:     method.DefineParameter(1, ParameterAttributes.In, "obj");
12:     method.DefineParameter(2, ParameterAttributes.In, "value");
13:     var setAction = (Action
)method.CreateDelegate(typeof(Action
));
14:     setAction(foo, bar);
15: }

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

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

1: public static Bar GetPropertyValueViaExpression(IFoo foo)
2: {
3:     var property = typeof(IFoo).GetProperty("Bar");
4:     var target = Expression.Parameter(typeof(IFoo));
5:     var getPropertyValue = Expression.Property(target, property);
6:     var getFunc = Expression.Lambda
>(getPropertyValue, target).Compile();
7:     return getFunc(foo);
8: }

下面则是基于IL Emit的版本:

1: public static Bar GetPropertyValueViaEmit(IFoo foo)
2: {
3:     var property = typeof(IFoo).GetProperty("Bar");
4:     DynamicMethod method = new DynamicMethod("GetValue", typeof(Bar), new Type[] { typeof(IFoo) });
5: 
6:     ILGenerator ilGenerator = method.GetILGenerator();
7:     ilGenerator.Emit(OpCodes.Ldarg_0);
8:     ilGenerator.EmitCall(OpCodes.Callvirt, property.GetGetMethod(), null);
9:     ilGenerator.Emit(OpCodes.Ret);
10: 
11:     method.DefineParameter(1, ParameterAttributes.In, "target");
12:     var getFunc = (Func
)method.CreateDelegate(typeof(Func
));
13:     return getFunc(foo);
14: }

四、看看两种写法对应的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文件。

1: static void Main()
2: {
3:     var property        = typeof(IFoo).GetProperty("Bar");
4:     var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Artech.EmitVsExpression"), AssemblyBuilderAccess.RunAndSave);
5:     var moduleBuilder   = assemblyBuilder.DefineDynamicModule("Artech.EmitVsExpression", "Artech.EmitVsExpression.dll");
6:     var typeBuilder     = moduleBuilder.DefineType("Program");
7: 
8:     //GetPropertyValueViaExpression
9:     var methodBuilder       = typeBuilder.DefineMethod("GetPropertyValueViaExpression", MethodAttributes.Static | MethodAttributes.Public, typeof(Bar), new Type[] { typeof(IFoo) });
10:     var target              = Expression.Parameter(typeof(IFoo));
11:     var getPropertyValue    = Expression.Property(target, property);
12:     Expression.Lambda
>(getPropertyValue, target).CompileToMethod(methodBuilder);
13: 
14:     //SetPropertyValueViaExpression
15:     methodBuilder           = typeBuilder.DefineMethod("SetPropertyValueViaExpression", MethodAttributes.Static | MethodAttributes.Public, typeof(void), new Type[] { typeof(IFoo), typeof(Bar) });
16:     target                  = Expression.Parameter(typeof(IFoo));
17:     var propertyValue       = Expression.Parameter(typeof(Bar));
18:     var setPropertyValue    = Expression.Call(target, property.GetSetMethod(), propertyValue);
19:     Expression.Lambda
>(setPropertyValue, target, propertyValue).CompileToMethod(methodBuilder);
20: 
21:     //GetPropertyValueViaEmit
22:     methodBuilder           = typeBuilder.DefineMethod("GetPropertyValueViaEmit", MethodAttributes.Static| MethodAttributes.Public, typeof(Bar), new Type[] { typeof(IFoo) });
23:     ILGenerator ilGenerator = methodBuilder.GetILGenerator();
24:     ilGenerator.Emit(OpCodes.Ldarg_0);
25:     ilGenerator.EmitCall(OpCodes.Callvirt, property.GetGetMethod(), null);
26:     ilGenerator.Emit(OpCodes.Ret);
27: 
28:     //SetPropertyValueViaEmit
29:     methodBuilder   = typeBuilder.DefineMethod("SetPropertyValueViaEmit", MethodAttributes.Static | MethodAttributes.Public, typeof(void), new Type[] { typeof(IFoo), typeof(Bar) });
30:     ilGenerator     = methodBuilder.GetILGenerator();
31:     ilGenerator.Emit(OpCodes.Ldarg_0);
32:     ilGenerator.Emit(OpCodes.Ldarg_1);
33:     ilGenerator.EmitCall(OpCodes.Callvirt, property.GetSetMethod(), null);
34:     ilGenerator.Emit(OpCodes.Ret);
35: 
36:     typeBuilder.CreateType();
37:     assemblyBuilder.Save("Artech.EmitVsExpression.dll");
38: }

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

1: .method public static class [EmitVsExpressionTree]Bar
2:         GetPropertyValueViaExpression(class [EmitVsExpressionTree]IFoo A_0) cil managed
3: {
4:   // Code size       7 (0x7)
5:   .maxstack  1
6:   IL_0000:  ldarg.0
7:   IL_0001:  callvirt   instance class [EmitVsExpressionTree]Bar [EmitVsExpressionTree]IFoo::get_Bar()
8:   IL_0006:  ret
9: } // end of method Program::GetPropertyValueViaExpression
10: 
11: .method public static class [EmitVsExpressionTree]Bar
12:         GetPropertyValueViaEmit(class [EmitVsExpressionTree]IFoo A_0) cil managed
13: {
14:   // Code size       7 (0x7)
15:   .maxstack  1
16:   IL_0000:  ldarg.0
17:   IL_0001:  callvirt   instance class [EmitVsExpressionTree]Bar [EmitVsExpressionTree]IFoo::get_Bar()
18:   IL_0006:  ret
19: } // end of method Program::GetPropertyValueViaEmit

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

1: .method public static void  SetPropertyValueViaExpression(class [EmitVsExpressionTree]IFoo A_0,
2:                                                           class [EmitVsExpressionTree]Bar A_1) cil managed
3: {
4:   // Code size       8 (0x8)
5:   .maxstack  2
6:   IL_0000:  ldarg.0
7:   IL_0001:  ldarg.1
8:   IL_0002:  callvirt   instance void [EmitVsExpressionTree]IFoo::set_Bar(class [EmitVsExpressionTree]Bar)
9:   IL_0007:  ret
10: } // end of method Program::SetPropertyValueViaExpression
11: 
12: .method public static void  SetPropertyValueViaEmit(class [EmitVsExpressionTree]IFoo A_0,
13:                                                     class [EmitVsExpressionTree]Bar A_1) cil managed
14: {
15:   // Code size       8 (0x8)
16:   .maxstack  2
17:   IL_0000:  ldarg.0
18:   IL_0001:  ldarg.1
19:   IL_0002:  callvirt   instance void [EmitVsExpressionTree]IFoo::set_Bar(class [EmitVsExpressionTree]Bar)
20:   IL_0007:  ret
21: } // end of method Program::SetPropertyValueViaEmit

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

1: internal class Program
2: {
3:     // Methods
4:     public static Bar GetPropertyValueViaEmit(IFoo foo1)
5:     {
6:         return foo1.Bar;
7:     }
8: 
9:     public static Bar GetPropertyValueViaExpression(IFoo foo1)
10:     {
11:         return foo1.Bar;
12:     }
13: 
14:     public static void SetPropertyValueViaEmit(IFoo foo1, Bar bar1)
15:     {
16:         foo1.Bar = bar1;
17:     }
18: 
19:     public static void SetPropertyValueViaExpression(IFoo foo1, Bar bar1)
20:     {
21:         foo1.Bar = bar1;
22:     }
23: }

转载于:https://www.cnblogs.com/artech/archive/2011/03/27/ExpressTreeVsIlEmit.html

你可能感兴趣的文章
后端程序员之路 12、K最近邻(k-Nearest Neighbour,KNN)分类算法
查看>>
缓存技术
查看>>
linux64需要增加的依赖库
查看>>
手机网站页面模板
查看>>
Huffman树与编码的简单实现
查看>>
__delattr__\__delitem__
查看>>
htmlunit简单百度搜索,网页解析
查看>>
Cocos2dx Android在编译的时候格式出错例如(snprintf)
查看>>
spring不同环境下用不同的配置文件
查看>>
数组_leetcode80
查看>>
SQL Error (1130): Host '192.168.1.100' is not allowed to connect to this MySQL server
查看>>
普通线程类获取service,controller等spring容器类
查看>>
Redis高级实践之————Redis短连接性能优化
查看>>
ThreadLocal使用
查看>>
POJ - 2155 Matrix(二维树状数组)
查看>>
基于Cat的分布式调用追踪
查看>>
建筑物联动
查看>>
汇编语言 手记5
查看>>
牛客网暑期ACM多校训练营(第三场) E-Sort String next数组的应用
查看>>
如何成功的捕捉一只女神
查看>>