關于Expression Tree和IL Emit的所謂的"性能差別"

作者: Artech  來源: 博客園  發布時間: 2011-03-27 21:39  閱讀: 3511 次  推薦: 2   原文鏈接   [收藏]  

  昨天寫了《三種屬性操作性能比較》,有個網友寫信問我一個問題:從性能上看,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
= 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");
}

  現在我們通過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

.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)
{

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;
}
}
2
0
 
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()