.Net4.0用表示式樹構建委託改善反射效能
轉自: http://www.cnblogs.com/lemontea/archive/2013/02/04/2891281.html
最近搞一個系統時由於在比較關鍵地方用到反射了,所以要關注了一下反射的效能問題。
.Net4.0反射效能改善
看老趙的文章,老趙得到的結果是這樣的:
00:00:00.0125539 (Directly invoke)
00:00:04.5349626 (Reflection invoke)
00:00:00.0322555 (Dynamic executor)
而我把程式碼搞下來自己執行得到這樣的結果:
這裡不是說機器效能造成絕對的時間,而是差距比例完全不一樣,想了一陣想起了老趙當時應該是基於.Net3.5,果斷把程式的目標框架切換到.Net3.5,結果如下:
00:00:00.0018801 (Directly invoke)
00:00:02.4288876 (Reflection invoke)
00:00:00.0141537 (Dynamic executor)
三者的差距仍然有些不一樣,老趙那邊的直接呼叫與動態執行同一數量級的結果還是沒有。但發現了另一些資訊。反射和直接呼叫方法.Net4.0比.Net3.5有非常大的改善,特別是反射,效能提升了好幾倍。反而構建表示式樹動態呼叫的方式效能比.Net3.5差了一點。但是相對反射還是有差距,按照這個比例,寫寫表示式樹還是值得的。
改善老趙的DynamicMethodExecutor
老趙的那篇的文章的思路是使用DynamicMethodExecutor來構造一個萬能的委託Func<object, object[], object>其中第一個引數是例項物件,第二是引數列表,第三是返回值。.Net4.0的表示式樹要比3.5的先進一點,經過一番改造發現是不需要這麼一個萬能委託的,直接用Expression.Lambda.Compile()編譯出來的Delegate強制轉換為強型別的委託來得更加簡單。全部程式碼一個方法即可,精簡了許多。
/// <summary> /// 動態構造委託 /// </summary> /// <param name="methodinfo">方法元資料</param> /// <returns>委託</returns> public static Delegate BuildDynamicDelegate(MethodInfo methodInfo) { if (methodInfo == null) throw new ArgumentNullException("methodInfo"); var paramExpressions = methodInfo.GetParameters().Select((p, i) => { var name = "param" + (i + 1).ToString(CultureInfo.InvariantCulture); return Expression.Parameter(p.ParameterType, name); }).ToList(); MethodCallExpression callExpression; if (methodInfo.IsStatic) { //Call(params....) callExpression = Expression.Call(methodInfo, paramExpressions); } else { var instanceExpression = Expression.Parameter(methodInfo.ReflectedType, "instance"); //insatnce.Call(params….) callExpression = Expression.Call(instanceExpression, methodInfo, paramExpressions); paramExpressions.Insert(0, instanceExpression); } var lambdaExpression = Expression.Lambda(callExpression, paramExpressions); return lambdaExpression.Compile(); }
使用時轉換為強型別的委託即可:
var action = (Action<TInstance, T1, T2>)BuildDynamicDelegate(methodInfo);
var func = (Func<TInstance, T1, T2, TReturn>)BuildDynamicDelegate(methodInfo);
老趙那個委託都是object,使用時的型別轉換,還有裝箱,拆箱都會有一定的效能損失,而強型別就沒有這個問題。
首先在老趙的那篇文章上一個方法改為兩個方法,然後測試:
public void Call1(object o1, object o2, object o3) { }
public void Call2(int o1, int o2, int o3) { }
private static void DynamicExecutor_ObjectType()
{
var executor = new DynamicMethodExecutor(Call1MethodInfo);
var watch1 = new Stopwatch();
watch1.Start();
for (var i = 0; i < Times; i++)
{
executor.Execute(ProgramInstance, ObjectParameters);
}
watch1.Stop();
Console.WriteLine(watch1.Elapsed + " (Dynamic executor(object))(JeffreyZhao)");
}
private static void DynamicExecutor_IntType()
{
var executor = new DynamicMethodExecutor(Call2MethodInfo);
var watch1 = new Stopwatch();
watch1.Start();
for (var i = 0; i < Times; i++)
{
executor.Execute(ProgramInstance, IntParameters);
}
watch1.Stop();
Console.WriteLine(watch1.Elapsed + " (Dynamic executor(int))(JeffreyZhao)");
}
private static void DynamicExecutor_StrongObject()
{
var action = DynamicMethodBuilder.BuildAction<Program, object, object, object>(Call1MethodInfo);
var watch1 = new Stopwatch();
watch1.Start();
for (var i = 0; i < Times; i++)
{
action(ProgramInstance, ObjectParameters[0], ObjectParameters[1], ObjectParameters[2]);
}
watch1.Stop();
Console.WriteLine(watch1.Elapsed + " (Dynamic executor(object))(zhangweiwen)");
}
private static void DynamicExecutor_StrongInt()
{
var action = DynamicMethodBuilder.BuildAction<Program, int, int, int>(Call2MethodInfo);
var watch1 = new Stopwatch();
watch1.Start();
for (var i = 0; i < Times; i++)
{
action(ProgramInstance, IntParameters1[0], IntParameters1[1], IntParameters1[2]);
}
watch1.Stop();
Console.WriteLine(watch1.Elapsed + " (Dynamic executor(int))(zhangweiwen)");
}
結果:
00:00:00.0188422 (Dynamic executor(object))(JeffreyZhao)
00:00:00.0210869 (Dynamic executor(int))(JeffreyZhao)
00:00:00.0142841 (Dynamic executor(object))(zhangweiwen)
00:00:00.0147589 (Dynamic executor(int))(zhangweiwen)
差距不大,但是還是有一定得改善,特別引數是int的方法,用了強型別後效能比較穩定,不會出現偏差。
構建委託動態賦值
既然有動態呼叫方法,同樣也可以動態賦值,而且據我的經驗,根據PropertyInfo的SetValue去反射設屬性值用得比反射呼叫方法更加頻繁。所以同樣需要有方法來動態構建委託改善效能。
幸好,.Net4.0提供了支援,.Net4.0新增了Expression.Assign來表示一個賦值表示式。有了它,構建起來比方法的更加簡單:
private static Action<TInstance, TProperty> BuildSetPropertyAction<TInstance, TProperty>(PropertyInfo propertyInfo)
{
var instanceParam = Expression.Parameter(typeof(TInstance), "instance");
var valueParam = Expression.Parameter(typeof(TProperty), "value");
//instance.Property
var propertyProperty = Expression.Property(instanceParam, propertyInfo);
//instance.Property = value
var assignExpression = Expression.Assign(propertyProperty, valueParam);
var lambdaExpression = Expression.Lambda<Action<TInstance, TProperty>>(assignExpression, instanceParam, valueParam);
return lambdaExpression.Compile();
}
直接返回了強型別的委託,所以使用起來更加簡單:
var action = BuildSetPropertyAction<Program, object>(ObjectPropertyInfo);
action(ProgramInstance, ObjectValue);
來測試一下效能:
private static void DirectlySetValueType()
{
var watch1 = new Stopwatch();
watch1.Start();
for (var i = 0; i < Times; i++)
{
ProgramInstance.IntProperty = IntValue;
}
watch1.Stop();
Console.WriteLine(watch1.Elapsed + " (Directly Set IntProperty)");
}
private static void ReflectionSetValueType()
{
var watch2 = new Stopwatch();
watch2.Start();
for (var i = 0; i < Times; i++)
{
IntPropertyInfo.SetValue(ProgramInstance, IntValue, null);
}
watch2.Stop();
Console.WriteLine(watch2.Elapsed + " (Reflection Set IntProperty)");
}
private static void DynamicSetValueType()
{
var action = BuildSetPropertyAction<Program, int>(IntPropertyInfo);
var watch1 = new Stopwatch();
watch1.Start();
for (var i = 0; i < Times; i++)
{
action(ProgramInstance, IntValue);
}
watch1.Stop();
Console.WriteLine(watch1.Elapsed + " (Dynamic Set IntProperty)");
}
private static void DirectlySetReferenceType()
{
var watch1 = new Stopwatch();
watch1.Start();
for (var i = 0; i < Times; i++)
{
ProgramInstance.ObjectProperty = ObjectValue;
}
watch1.Stop();
Console.WriteLine(watch1.Elapsed + " (Directly Set ObjectProperty)");
}
private static void ReflectionSetReferenceType()
{
var watch2 = new Stopwatch();
watch2.Start();
for (var i = 0; i < Times; i++)
{
ObjectPropertyInfo.SetValue(ProgramInstance, ObjectValue, null);
}
watch2.Stop();
Console.WriteLine(watch2.Elapsed + " (Reflection Set ObjectProperty)");
}
private static void DynamicSetReferenceType()
{
var action = BuildSetPropertyAction<Program, object>(ObjectPropertyInfo);
//action(ProgramInstance, ObjectValue);
var watch1 = new Stopwatch();
watch1.Start();
for (var i = 0; i < Times; i++)
{
action(ProgramInstance, ObjectValue);
}
watch1.Stop();
Console.WriteLine(watch1.Elapsed + " (Dynamic Set ObjectProperty)");
}
結果如下:
Test Set Value:
00:00:00.0003237 (Directly Set IntProperty)
00:00:00.3160570 (Reflection Set IntProperty)
00:00:00.0132668 (Dynamic Set IntProperty)
-----
00:00:00.0028183 (Directly Set ObjectProperty)
00:00:00.2937783 (Reflection Set ObjectProperty)
00:00:00.0150118 (Dynamic Set ObjectProperty)
雖然跟直接賦值不能比,但比反射快大概30倍。
全部程式碼,希望對大家有幫助.