利用表达式树构建委托改善反射性能

最近搞一个系统时由于在比较关键地方用到反射了,所以要关注了一下反射的性能问题。搜索一下,不难搜到老赵的这篇文章,下面是一些杂乱的笔记。(建议先看老赵的文章)

.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)
三者的差距仍然有些不一样,老赵那边的直接调用与动态执行同一数量级的结果还是没有。但发现了另一些信息。反射和直接调用方法.Net4.0比.Net3.5有非常大的改善,特别是反射,性能提升了好几倍。反而构建表达式树动态调用的方式性能比.Net3.5差了一点。但是相对反射还是有差距,按照这个比例,写写表达式树还是值得的。

改善老赵的DynamicMethodExecutor

老赵的那篇的文章的思路是使用DynamicMethodExecutor来构造一个万能的委托Func<object, object[], object>其中第一个参数是实例对象,第二是参数列表,第三是返回值。.Net4.0的表达式树要比3.5的先进一点,经过一番改造发现是不需要这么一个万能委托的,直接用Expression.Lambda.Compile()编译出来的Delegate强制转换为强类型的委托来得更加简单。全部代码一个方法即可,精简了许多。

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 &lt; 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 &lt; Times; i++)
     {
         IntPropertyInfo.SetValue(ProgramInstance, IntValue, null );
     }
     watch2.Stop();
     Console.WriteLine(watch2.Elapsed + " (Reflection Set IntProperty)" );
}
 
private  static  void  DynamicSetValueType()
{
     var  action = BuildSetPropertyAction&lt;Program, int &gt;(IntPropertyInfo);
     var  watch1 = new  Stopwatch();
     watch1.Start();
     for  ( var  i = 0; i &lt; 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 &lt; 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 &lt; Times; i++)
     {
         ObjectPropertyInfo.SetValue(ProgramInstance, ObjectValue, null );
     }
     watch2.Stop();
     Console.WriteLine(watch2.Elapsed + " (Reflection Set ObjectProperty)" );
}
 
private  static  void  DynamicSetReferenceType()
{
     var  action = BuildSetPropertyAction&lt;Program, object &gt;(ObjectPropertyInfo);
     //action(ProgramInstance, ObjectValue);
     var  watch1 = new  Stopwatch();
     watch1.Start();
     for  ( var  i = 0; i &lt; 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);

原文: http://www.cnblogs.com/lemontea/archive/2013/02/04/2891281.html




  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值