利用表达式树构建委托改善反射性能
最近搞一个系统时由于在比较关键地方用到反射了,所以要关注了一下反射的性能问题。搜索一下,不难搜到老赵的这篇文章,下面是一些杂乱的笔记。(建议先看老赵的文章)
.Net4.0反射性能改善
看老赵的文章,老赵得到的结果是这样的:
1 2 3 | 00:00:00.0125539 (Directly invoke) 00:00:04.5349626 (Reflection invoke) 00:00:00.0322555 (Dynamic executor) |
而我把代码搞下来自己运行得到这样的结果:
1 2 3 | 00:00:00.0009710 (Directly invoke) 00:00:00.4142893 (Reflection invoke) 00:00:00.0194501 (Dynamic executor) |
这里不是说机器性能造成绝对的时间,而是差距比例完全不一样,想了一阵想起了老赵当时应该是基于.Net3.5,果断把程序的目标框架切换到.Net3.5,结果如下:
1 2 3 | 00:00:00.0018801 (Directly invoke) 00:00:02.4288876 (Reflection invoke) 00:00:00.0141537 (Dynamic executor) |
改善老赵的DynamicMethodExecutor
老赵的那篇的文章的思路是使用DynamicMethodExecutor来构造一个万能的委托Func
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | /// <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(); } |
使用时转换为强类型的委托即可:
1 2 | var action = (Action<TInstance, T1, T2>)BuildDynamicDelegate(methodInfo); var func = (Func<TInstance, T1, T2, TReturn>)BuildDynamicDelegate(methodInfo); |
老赵那个委托都是object,使用时的类型转换,还有装箱,拆箱都会有一定的性能损失,而强类型就没有这个问题。
首先在老赵的那篇文章上一个方法改为两个方法,然后测试:
1 2 | public void Call1( object o1, object o2, object o3) { } public void Call2( int o1, int o2, int o3) { } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | 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)" ); } |
结果:
1 2 3 4 | 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来表示一个赋值表达式。有了它,构建起来比方法的更加简单:
1 2 3 4 5 6 7 8 9 10 11 | 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(); } |
直接返回了强类型的委托,所以使用起来更加简单:
1 2 | var action = BuildSetPropertyAction<Program, object >(ObjectPropertyInfo); action(ProgramInstance, ObjectValue); |
来测试一下性能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | 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)" ); } |
结果如下:
1 2 3 4 5 6 7 8 | 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倍。
全部代码
希望对大家有帮助
The End。
===重要更新===
我把上面的方法用在项目中才发现陷入了一个悖论。就是往往需要使用反射的上下文中是没有实例类型的,而有了实例类型的上下文中有往往不需要反射。所以构建表达式树是需要去掉实例的类型参数,在表达式树中做类型转换,这样会有一点点的性能损失,但同时又带来一个好处,使用时类型参数少了一个,写起来方便了一点。两个主要代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | /// <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( typeof ( object ), "instance" ); //((T)instance) var castExpression = Expression.Convert(instanceExpression, methodInfo.ReflectedType); //((T)instance).Call(params) callExpression = Expression.Call(castExpression, methodInfo, paramExpressions); paramExpressions.Insert(0, instanceExpression); } var lambdaExpression = Expression.Lambda(callExpression, paramExpressions); return lambdaExpression.Compile(); } //使用 public static Action< object > BuildAction(MethodInfo methodInfo) { return (Action< object >)BuildDynamicDelegate(methodInfo); } public static Action< object , T1> BuildAction<T1>(MethodInfo methodInfo) { return (Action< object , T1>)BuildDynamicDelegate(methodInfo); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | /// <summary> /// 动态构造赋值委托 /// </summary> /// <typeparam name="TProperty">属性类型</typeparam> /// <param name="propertyInfo">属性元数据</param> /// <returns>强类型委托</returns> public static Action< object , TProperty> BuildSetPropertyAction<TProperty>(PropertyInfo propertyInfo) { var instanceParam = Expression.Parameter( typeof ( object ), "instance" ); var valueParam = Expression.Parameter( typeof (TProperty), "value" ); //((T)instance) var castExpression = Expression.Convert(instanceParam, propertyInfo.ReflectedType); //((T)instance).Property var propertyProperty = Expression.Property(castExpression, propertyInfo); //((T)instance).Property = value var assignExpression = Expression.Assign(propertyProperty, valueParam); var lambdaExpression = Expression.Lambda<Action< object , TProperty>>(assignExpression, instanceParam, valueParam); return lambdaExpression.Compile(); } //使用 var action = BuildSetPropertyAction< int >(IntPropertyInfo); action(ProgramInstance, IntValue); |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验