反射(4)反射性能问题:直接调用vs反射调用
很多人都说使用反射会有性能问题,那到底会比直接调用慢多少呢,下面就来测试一下。
直接调用vs反射调用
下面就来写个demo来验证下直接调用和反射调用的性能差异,代码如下:
1 namespace ConsoleApplication7
2 {
3 class Program
4 {
5 static void Main(string[] args)
6 {
7 //比较直接调用和反射调用的性能差异
8 //7ms vs 365ms
9 int times = 1000000;
10 var program = new Program();
11 CodeTimerHelper.Initialize();
12
13 CodeTimerHelper.Time("直接调用", times, () =>
14 {
15 program.Call();
16 });
17
18 var t = typeof(Program);
19 var obj = Activator.CreateInstance(t);
20 CodeTimerHelper.Time("反射调用", times, () =>
21 {
22 t.InvokeMember("Call", BindingFlags.InvokeMethod, null, obj, null);
23 });
24
25 Console.ReadKey();
26 }
27
28 /// <summary>
29 /// 测试方法
30 /// </summary>
31 public void Call()
32 {
33 }
34
35 }
36 }
测试结果:

从100万次调用结果来看,确实就像很多人所说的,两者在性能上具有数量级的差距。
为什么反射有性能损失
既然反射性能有损失,那具体损失在哪里呢?
1,反射是基于程序集和元数据的,在使用反射的时候,会搜索元数据,而元数据是基于字符串的,并且无法预编译,所以这一系列的操作对性能有影响。
2,大量的装箱拆箱也对性能有影响。由于我们对目标类型是未知的,而且向方法传递的参数通常是object类型的,所以会有大量的装箱和拆箱。
反射性能优化方案
我们已经知道使用反射有性能问题,但是有些场景下又不得不使用反射技术,所以要想办法优化反射性能。
这里就引用老赵公开的System.Linq.Expressions.Expression<TDelegate>表达式树的类,与直接调用进行对比,代码如下:
1 //3,基于表达式树
2 var methodInfo = t.GetMethod("Call");
3 var executor = new DynamicMethodExecutor(methodInfo);
4 CodeTimerHelper.Time("Dynamic executor", times, () =>
5 {
6 executor.Execute(obj, null);
7 });
测试结果:

哇,同样的100万次调用,使用DynamicMethodExecutor调用跟直接调用的性能相差无几。
附DynamicMethodExecutor的封装代码:
1 /// <summary>
2 ///
3 /// </summary>
4 public class DynamicMethodExecutor
5 {
6 private Func<object, object[], object> m_execute;
7
8 public DynamicMethodExecutor(MethodInfo methodInfo)
9 {
10 this.m_execute = this.GetExecuteDelegate(methodInfo);
11 }
12
13 public object Execute(object instance, object[] parameters)
14 {
15 return this.m_execute(instance, parameters);
16 }
17
18 private Func<object, object[], object> GetExecuteDelegate(MethodInfo methodInfo)
19 {
20 // parameters to execute
21 ParameterExpression instanceParameter = Expression.Parameter(typeof(object), "instance");
22 ParameterExpression parametersParameter = Expression.Parameter(typeof(object[]), "parameters");
23
24 // build parameter list
25 List<Expression> parameterExpressions = new List<Expression>();
26 ParameterInfo[] paramInfos = methodInfo.GetParameters();
27 for (int i = 0; i < paramInfos.Length; i++)
28 {
29 // (Ti)parameters[i]
30 BinaryExpression valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));
31 UnaryExpression valueCast = Expression.Convert(valueObj, paramInfos[i].ParameterType);
32 parameterExpressions.Add(valueCast);
33 }
34
35 // non-instance for static method, or ((TInstance)instance)
36 Expression instanceCast = methodInfo.IsStatic ? null : Expression.Convert(instanceParameter, methodInfo.ReflectedType);
37
38 // static invoke or ((TInstance)instance).Method
39 MethodCallExpression methodCall = Expression.Call(instanceCast, methodInfo, parameterExpressions);
40
41 // ((TInstance)instance).Method((T0)parameters[0], (T1)parameters[1], ...)
42 if (methodCall.Type == typeof(void))
43 {
44 Expression<Action<object, object[]>> lambda = Expression.Lambda<Action<object, object[]>>(methodCall, instanceParameter, parametersParameter);
45 Action<object, object[]> execute = lambda.Compile();
46 return (instance, parameters) =>
47 {
48 execute(instance, parameters);
49 return null;
50 };
51 }
52 else
53 {
54 UnaryExpression castMethodCall = Expression.Convert(methodCall, typeof(object));
55 Expression<Func<object, object[], object>> lambda = Expression.Lambda<Func<object, object[], object>>(castMethodCall, instanceParameter, parametersParameter);
56 return lambda.Compile();
57 }
58 }
除了使用linq的表达式树生成Delegate的方法外,还有比如,CodeDom生成代码并动态编译,或者使用Emit来直接编写IL的方法来提高反射的性能,但是相对来说,上面这个方法是最简单的。
至此,整个反射的总结就完成了!
参考文章
漫思

浙公网安备 33010602011771号