首页 > 代码库 > [转] 利用表达式树构建委托改善反射性能
[转] 利用表达式树构建委托改善反射性能
最近搞一个系统时由于在比较关键地方用到反射了,所以要关注了一下反射的性能问题。搜索一下,不难搜到老赵的这篇文章,下面是一些杂乱的笔记。(建议先看老赵的文章)
.Net4.0反射性能改善
看老赵的文章,老赵得到的结果是这样的:
1 00:00:00.0125539 (Directly invoke)2 00:00:04.5349626 (Reflection invoke)3 00:00:00.0322555 (Dynamic executor)
而我把代码搞下来自己运行得到这样的结果:
1 00:00:00.0009710 (Directly invoke)2 00:00:00.4142893 (Reflection invoke)3 00:00:00.0194501 (Dynamic executor)
这里不是说机器性能造成绝对的时间,而是差距比例完全不一样,想了一阵想起了老赵当时应该是基于.Net3.5,果断把程序的目标框架切换到.Net3.5,结果如下:
1 00:00:00.0018801 (Directly invoke)2 00:00:02.4288876 (Reflection invoke)3 00:00:00.0141537 (Dynamic executor)
三者的差距仍然有些不一样,老赵那边的直接调用与动态执行同一数量级的结果还是没有。但发现了另一些信息。反射和直接调用方法.Net4.0比.Net3.5有非常大的改善,特别是反射,性能提升了好几倍。反而构建表达式树动态调用的方式性能比.Net3.5差了一点。但是相对反射还是有差距,按照这个比例,写写表达式树还是值得的。
改善老赵的DynamicMethodExecutor
老赵的那篇的文章的思路是使用DynamicMethodExecutor来构造一个万能的委托Func<object, object[],="" object=""><object, object[], object>其中第一个参数是实例对象,第二是参数列表,第三是返回值。.Net4.0的表达式树要比3.5的先进一点,经过一番改造发现是不需要这么一个万能委托的,直接用Expression.Lambda.Compile()编译出来的Delegate强制转换为强类型的委托来得更加简单。全部代码一个方法即可,精简了许多。
1 /// <summary> 2 /// 动态构造委托 3 /// </summary> 4 /// <param name="methodInfo">方法元数据</param> 5 /// <returns>委托</returns> 6 public static Delegate BuildDynamicDelegate(MethodInfo methodInfo) 7 { 8 if (methodInfo == null) 9 throw new ArgumentNullException("methodInfo");10 11 var paramExpressions = methodInfo.GetParameters().Select((p, i) =>12 {13 var name = "param" + (i + 1).ToString(CultureInfo.InvariantCulture);14 return Expression.Parameter(p.ParameterType, name);15 }).ToList();16 17 MethodCallExpression callExpression;18 if (methodInfo.IsStatic)19 {20 //Call(params....)21 callExpression = Expression.Call(methodInfo, paramExpressions);22 }23 else24 {25 var instanceExpression = Expression.Parameter(methodInfo.ReflectedType, "instance");26 //insatnce.Call(params….)27 callExpression = Expression.Call(instanceExpression, methodInfo, paramExpressions);28 paramExpressions.Insert(0, instanceExpression);29 }30 var lambdaExpression = Expression.Lambda(callExpression, paramExpressions);31 return lambdaExpression.Compile();32 }
使用时转换为强类型的委托即可:
1 var action = (Action<TInstance, T1, T2>)BuildDynamicDelegate(methodInfo);2 var func = (Func<TInstance, T1, T2, TReturn>)BuildDynamicDelegate(methodInfo);
老赵那个委托都是object,使用时的类型转换,还有装箱,拆箱都会有一定的性能损失,而强类型就没有这个问题。
首先在老赵的那篇文章上一个方法改为两个方法,然后测试:
1 public void Call1(object o1, object o2, object o3) { }2 public void Call2(int o1, int o2, int o3) { }
1 private static void DynamicExecutor_ObjectType() 2 { 3 var executor = new DynamicMethodExecutor(Call1MethodInfo); 4 var watch1 = new Stopwatch(); 5 watch1.Start(); 6 for (var i = 0; i < Times; i++) 7 { 8 executor.Execute(ProgramInstance, ObjectParameters); 9 }10 watch1.Stop();11 Console.WriteLine(watch1.Elapsed + " (Dynamic executor(object))(JeffreyZhao)");12 }13 private static void DynamicExecutor_IntType()14 {15 var executor = new DynamicMethodExecutor(Call2MethodInfo);16 var watch1 = new Stopwatch();17 watch1.Start();18 for (var i = 0; i < Times; i++)19 {20 executor.Execute(ProgramInstance, IntParameters);21 }22 watch1.Stop();23 Console.WriteLine(watch1.Elapsed + " (Dynamic executor(int))(JeffreyZhao)");24 }25 private static void DynamicExecutor_StrongObject()26 {27 var action = DynamicMethodBuilder.BuildAction<Program, object, object, object>(Call1MethodInfo);28 var watch1 = new Stopwatch();29 watch1.Start();30 for (var i = 0; i < Times; i++)31 {32 action(ProgramInstance, ObjectParameters[0], ObjectParameters[1], ObjectParameters[2]);33 }34 watch1.Stop();35 Console.WriteLine(watch1.Elapsed + " (Dynamic executor(object))(zhangweiwen)");36 }37 38 private static void DynamicExecutor_StrongInt()39 {40 var action = DynamicMethodBuilder.BuildAction<Program, int, int, int>(Call2MethodInfo);41 var watch1 = new Stopwatch();42 watch1.Start();43 for (var i = 0; i < Times; i++)44 {45 action(ProgramInstance, IntParameters1[0], IntParameters1[1], IntParameters1[2]);46 }47 watch1.Stop();48 Console.WriteLine(watch1.Elapsed + " (Dynamic executor(int))(zhangweiwen)");49 }
结果:
1 00:00:00.0188422 (Dynamic executor(object))(JeffreyZhao)2 00:00:00.0210869 (Dynamic executor(int))(JeffreyZhao)3 00:00:00.0142841 (Dynamic executor(object))(zhangweiwen)4 00:00:00.0147589 (Dynamic executor(int))(zhangweiwen)
差距不大,但是还是有一定得改善,特别参数是int的方法,用了强类型后性能比较稳定,不会出现偏差。
构建委托动态赋值
既然有动态调用方法,同样也可以动态赋值,而且据我的经验,根据PropertyInfo的SetValue去反射设属性值用得比反射调用方法更加频繁。所以同样需要有方法来动态构建委托改善性能。
幸好,.Net4.0提供了支持,.Net4.0新增了Expression.Assign来表示一个赋值表达式。有了它,构建起来比方法的更加简单:
1 private static Action<TInstance, TProperty> BuildSetPropertyAction<TInstance, TProperty>(PropertyInfo propertyInfo) 2 { 3 var instanceParam = Expression.Parameter(typeof(TInstance), "instance"); 4 var valueParam = Expression.Parameter(typeof(TProperty), "value"); 5 //instance.Property 6 var propertyProperty = Expression.Property(instanceParam, propertyInfo); 7 //instance.Property = value 8 var assignExpression = Expression.Assign(propertyProperty, valueParam); 9 var lambdaExpression = Expression.Lambda<Action<TInstance, TProperty>>(assignExpression, instanceParam, valueParam);10 return lambdaExpression.Compile();11 }
直接返回了强类型的委托,所以使用起来更加简单:
1 var action = BuildSetPropertyAction<Program, object>(ObjectPropertyInfo);2 action(ProgramInstance, ObjectValue);
来测试一下性能:
1 private static void DirectlySetValueType() 2 { 3 var watch1 = new Stopwatch(); 4 watch1.Start(); 5 for (var i = 0; i < Times; i++) 6 { 7 ProgramInstance.IntProperty = IntValue; 8 } 9 watch1.Stop();10 Console.WriteLine(watch1.Elapsed + " (Directly Set IntProperty)");11 }12 13 private static void ReflectionSetValueType()14 {15 var watch2 = new Stopwatch();16 watch2.Start();17 for (var i = 0; i < Times; i++)18 {19 IntPropertyInfo.SetValue(ProgramInstance, IntValue, null);20 }21 watch2.Stop();22 Console.WriteLine(watch2.Elapsed + " (Reflection Set IntProperty)");23 }24 25 private static void DynamicSetValueType()26 {27 var action = BuildSetPropertyAction<Program, int>(IntPropertyInfo);28 var watch1 = new Stopwatch();29 watch1.Start();30 for (var i = 0; i < Times; i++)31 {32 action(ProgramInstance, IntValue);33 }34 watch1.Stop();35 Console.WriteLine(watch1.Elapsed + " (Dynamic Set IntProperty)");36 }37 38 private static void DirectlySetReferenceType()39 {40 var watch1 = new Stopwatch();41 watch1.Start();42 for (var i = 0; i < Times; i++)43 {44 ProgramInstance.ObjectProperty = ObjectValue;45 }46 watch1.Stop();47 Console.WriteLine(watch1.Elapsed + " (Directly Set ObjectProperty)");48 }49 50 private static void ReflectionSetReferenceType()51 {52 var watch2 = new Stopwatch();53 watch2.Start();54 for (var i = 0; i < Times; i++)55 {56 ObjectPropertyInfo.SetValue(ProgramInstance, ObjectValue, null);57 }58 watch2.Stop();59 Console.WriteLine(watch2.Elapsed + " (Reflection Set ObjectProperty)");60 }61 62 private static void DynamicSetReferenceType()63 {64 var action = BuildSetPropertyAction<Program, object>(ObjectPropertyInfo);65 //action(ProgramInstance, ObjectValue);66 var watch1 = new Stopwatch();67 watch1.Start();68 for (var i = 0; i < Times; i++)69 {70 action(ProgramInstance, ObjectValue);71 }72 watch1.Stop();73 Console.WriteLine(watch1.Elapsed + " (Dynamic Set ObjectProperty)");74 }
结果如下:
1 Test Set Value:2 00:00:00.0003237 (Directly Set IntProperty)3 00:00:00.3160570 (Reflection Set IntProperty)4 00:00:00.0132668 (Dynamic Set IntProperty)5 -----6 00:00:00.0028183 (Directly Set ObjectProperty)7 00:00:00.2937783 (Reflection Set ObjectProperty)8 00:00:00.0150118 (Dynamic Set ObjectProperty)
虽然跟直接赋值不能比,但比反射快大概30倍。
全部代码
希望对大家有帮助
The End。
===重要更新===
我把上面的方法用在项目中才发现陷入了一个悖论。就是往往需要使用反射的上下文中是没有实例类型的,而有了实例类型的上下文中有往往不需要反射。所以构建表达式树是需要去掉实例的类型参数,在表达式树中做类型转换,这样会有一点点的性能损失,但同时又带来一个好处,使用时类型参数少了一个,写起来方便了一点。两个主要代码如下:
1 /// <summary> 2 /// 动态构造委托 3 /// </summary> 4 /// <param name="methodInfo">方法元数据</param> 5 /// <returns>委托</returns> 6 public static Delegate BuildDynamicDelegate(MethodInfo methodInfo) 7 { 8 if (methodInfo == null) 9 throw new ArgumentNullException("methodInfo");10 11 var paramExpressions = methodInfo.GetParameters().Select((p, i) =>12 {13 var name = "param" + (i + 1).ToString(CultureInfo.InvariantCulture);14 return Expression.Parameter(p.ParameterType, name);15 }).ToList();16 17 MethodCallExpression callExpression;18 if (methodInfo.IsStatic)19 {20 //Call(params....)21 callExpression = Expression.Call(methodInfo, paramExpressions);22 }23 else24 {25 var instanceExpression = Expression.Parameter(typeof(object), "instance");26 //((T)instance)27 var castExpression = Expression.Convert(instanceExpression, methodInfo.ReflectedType);28 //((T)instance).Call(params)29 callExpression = Expression.Call(castExpression, methodInfo, paramExpressions);30 paramExpressions.Insert(0, instanceExpression);31 }32 var lambdaExpression = Expression.Lambda(callExpression, paramExpressions);33 return lambdaExpression.Compile();34 }35 36 //使用37 public static Action<object> BuildAction(MethodInfo methodInfo)38 {39 return (Action<object>)BuildDynamicDelegate(methodInfo);40 }41 42 public static Action<object, T1> BuildAction<T1>(MethodInfo methodInfo)43 {44 return (Action<object, T1>)BuildDynamicDelegate(methodInfo);45 }
1 /// <summary> 2 /// 动态构造赋值委托 3 /// </summary> 4 /// <typeparam name="TProperty">属性类型</typeparam> 5 /// <param name="propertyInfo">属性元数据</param> 6 /// <returns>强类型委托</returns> 7 public static Action<object, TProperty> BuildSetPropertyAction<TProperty>(PropertyInfo propertyInfo) 8 { 9 var instanceParam = Expression.Parameter(typeof(object), "instance");10 var valueParam = Expression.Parameter(typeof(TProperty), "value");11 //((T)instance)12 var castExpression = Expression.Convert(instanceParam, propertyInfo.ReflectedType);13 //((T)instance).Property14 var propertyProperty = Expression.Property(castExpression, propertyInfo);15 //((T)instance).Property = value16 var assignExpression = Expression.Assign(propertyProperty, valueParam);17 var lambdaExpression = Expression.Lambda<Action<object, TProperty>>(assignExpression, instanceParam, valueParam);18 return lambdaExpression.Compile();19 }20 21 //使用22 var action = BuildSetPropertyAction<int>(IntPropertyInfo);23 action(ProgramInstance, IntValue);
[转] 利用表达式树构建委托改善反射性能