关于Expression Tree和IL Emit的所谓的"性能差别&quot“.NET研究”;

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

  昨天写了《三种属性操作性能比较》,有个网友写信问我一个问题:从性能上看,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的基础上推出了很多语言层面的特性,比如自动实现属性:

 
 
public class Foo
{
public Bar Bar{ get ; set ;}
public Foo()
{
this .Bar = new Bar();
}
}

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

 
 
public class Foo
{
private Bar _bar;
public Bar Bar
{
get { return _bar;}
set {_bar = value;}
}
public Foo()
{
_bar
= new Bar();
}
}

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

 
 
public interface IFoo
{
Bar{
get ; set ;}
}
public class Bar{}

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

 
 
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);
}

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

 
 
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);
}

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

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

 
 
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);
}

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

 
 
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);
}

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

 
 
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
= Ex上海闵行企业网站制作pression.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 " );
}

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

 
 
.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 上海徐汇企业网站设计与制作="color: #008000;">

.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

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

 
 
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

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

 
 
internal 上海徐汇企业网站制作> class Program
{
// Methods
public static Bar GetPropertyValueViaEmit(IFoo foo1)
{
return foo1.Bar;
}
public static Bar GetPropertyValueViaExpression(IFoo foo1)
{
上海闵行企业网站设计与制作span>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;
}
}
目录
相关文章
|
19天前
|
算法 Java 测试技术
Benchmark.NET:让 C# 测试程序性能变得既酷又简单
Benchmark.NET是一款专为 .NET 平台设计的性能基准测试框架,它可以帮助你测量代码的执行时间、内存使用情况等性能指标。它就像是你代码的 "健身教练",帮助你找到瓶颈,优化性能,让你的应用跑得更快、更稳!希望这个小教程能让你在追求高性能的路上越走越远,享受编程带来的无限乐趣!
68 13
|
4月前
|
SQL 缓存 开发框架
分享一个 .NET EF6 应用二级缓存提高性能的方法
分享一个 .NET EF6 应用二级缓存提高性能的方法
|
1月前
|
开发框架 安全 .NET
在数字化时代,.NET 技术凭借跨平台兼容性、丰富的开发工具和框架、高效的性能及强大的安全稳定性,成为软件开发的重要支柱
在数字化时代,.NET 技术凭借跨平台兼容性、丰富的开发工具和框架、高效的性能及强大的安全稳定性,成为软件开发的重要支柱。它不仅加速了应用开发进程,提升了开发质量和可靠性,还促进了创新和业务发展,培养了专业人才和技术社区,为软件开发和数字化转型做出了重要贡献。
27 5
|
1月前
|
传感器 人工智能 供应链
.NET开发技术在数字化时代的创新作用,从高效的开发环境、强大的性能表现、丰富的库和框架资源等方面揭示了其关键优势。
本文深入探讨了.NET开发技术在数字化时代的创新作用,从高效的开发环境、强大的性能表现、丰富的库和框架资源等方面揭示了其关键优势。通过企业级应用、Web应用及移动应用的创新案例,展示了.NET在各领域的广泛应用和巨大潜力。展望未来,.NET将与新兴技术深度融合,拓展跨平台开发,推动云原生应用发展,持续创新。
35 4
|
1月前
|
机器学习/深度学习 人工智能 Cloud Native
在数字化时代,.NET 技术凭借其跨平台兼容性、丰富的类库和工具集以及卓越的性能与效率,成为软件开发的重要平台
在数字化时代,.NET 技术凭借其跨平台兼容性、丰富的类库和工具集以及卓越的性能与效率,成为软件开发的重要平台。本文深入解析 .NET 的核心优势,探讨其在企业级应用、Web 开发及移动应用等领域的应用案例,并展望未来在人工智能、云原生等方面的发展趋势。
39 3
|
1月前
|
开发框架 安全 Java
.NET技术的独特魅力与优势,涵盖高效的开发体验、强大的性能表现、高度的可扩展性及丰富的生态系统等方面,展示了其在软件开发领域的核心竞争力
本文深入探讨了.NET技术的独特魅力与优势,涵盖高效的开发体验、强大的性能表现、高度的可扩展性及丰富的生态系统等方面,展示了其在软件开发领域的核心竞争力。.NET不仅支持跨平台开发,具备出色的安全性和稳定性,还能与多种技术无缝集成,为企业级应用提供全面支持。
36 3
|
1月前
|
人工智能 Java 编译器
.NET 9 发布 性能提升、AI 支持与全方位改进
【11月更文挑战第5天】.NET 9 引入了多项改进,包括性能提升、AI 支持和全方位功能优化。性能方面,编译器增强、服务器 GC 优化、矢量化和硬件支持等提升了执行效率。AI 方面,新增学习材料、合作伙伴生态、原生支持和生成式 AI 集成。此外,.NET Aspire 组件升级、编程语言新功能和开发工具更新进一步提升了开发体验。
|
4月前
|
开发者 C# Android开发
Xamarin 与 .NET:解锁现代化移动应用开发的超级武器——深入探讨C#与.NET框架如何赋能跨平台应用,实现高效编码与卓越性能
【8月更文挑战第31天】Xamarin 与 .NET 的结合为开发者提供了强大的平台,用于构建现代化移动应用。通过 C# 和 .NET 框架,Xamarin 可以实现一次编写、多平台运行,覆盖 iOS、Android 和 Windows。这种方式不仅节省了开发时间和成本,还保证了应用的一致性和高质量。Xamarin 是一个开源框架,专为跨平台移动应用开发设计,允许使用 C# 语言和 .NET 核心库构建原生应用,并访问各平台特定功能。微软维护的 Xamarin 是 Visual Studio 生态系统的一部分,极大地提高了开发效率。
93 0
|
4月前
|
开发框架 缓存 .NET
【App Service】在Azure App Service中分析.NET应用程序的性能的好帮手(Review Stack Traces)
【App Service】在Azure App Service中分析.NET应用程序的性能的好帮手(Review Stack Traces)
|
4月前
分享一份 .NET Core 简单的自带日志系统配置,平时做一些测试或个人代码研究,用它就可以了
分享一份 .NET Core 简单的自带日志系统配置,平时做一些测试或个人代码研究,用它就可以了