51Testing软件测试论坛

标题: 【转】替代反射调用的几种方式及性能测试 [打印本页]

作者: lsekfe    时间: 2016-10-26 10:55
标题: 【转】替代反射调用的几种方式及性能测试
园子里和这个话题的相关文章比较多,本文是旧话重提,外加个小的总结。主要因为近期看到很多同事、朋友都已经使用 VS2012 进行 .NET 4.5 开发了,却还在大量使用反射,不知道用新的方式。或有所了解,但又害怕性能不好不敢大胆去用。
本文以如下类为例:
  1. public class MyMath
  2. {
  3.     public int Add(int a, int b)
  4.     {
  5.         return a + b;
  6.     }
  7. }
复制代码
替代反射的几种方式
倒序说吧,从最先进最简单的开始。

1. dynamic 调用

.NET 4 引入了 dynamic 类型,可以使用如下方式来完成对 MyMath.Add 方法的动态调用:
  1. dynamic math = new MyMath();
  2. int result = math.Add(1, 2);
复制代码
非常简单,效率也不错,可以看后面的性能对比测试结果。
但有一点要注意, dynamic 遵守 .NET 的访问级别限定,会对成员进行可见性检查。也就是说,只能 dynamic 调用 public 成员;当然,如果是同一程序集内部,internal 成员也是可以访问的。
2. Expression Tree 编译调用
Expression Tree 是 .NET 3.5 引入的。简单地,我们可以使用 lambda 构建一颗 Expression Tree:
  1. var math = new MyMath();
  2. Expression<Func<int, int, int>> add = (a, b) => math.Add(a, b);
复制代码
这种方法适合手工编码构建,还有另外一种方式可以动态构建:
  1. var add = typeof(MyMath).GetMethod("Add");
  2. var math = Expression.Parameter(typeof(MyMath));
  3. var a = Expression.Parameter(typeof(int), "a");
  4. var b = Expression.Parameter(typeof(int), "b");
  5. var body = Expression.Call(myMath, add, a, b);
  6. var lambda = Expression.Lambda<Func<MyMath, int, int, int>>(body, math, a, b);
复制代码
两种方式构建出的 Tree 是相同的。
话归正题,构建出表达式树后,调用其 Compile 方法便可编译成一个委托,如下代码第 3 行:
  1. var math = new MyMath();
  2. Expression<Func<int, int, int>> addExpTree = (a, b) => math.Add(a, b);  // ExressionTree
  3. Func<int, int, int> add = addExpTree.Compile();                         //  编译成委托
  4. var result = add(1, 2);                                                 // 相加,结果为3
复制代码
与 dynamic 调用方法同,Expression Tree 编译出的委托方法也遵守 .NET 的访问级别限定,会对成员进行可见性检查,不能访问私有成员。
3. 反射发出调用
这里只介绍反射发出的一项技术 DynamicMethod,.NET 2.0 新增此类。
使用 DynamicMethod 类在运行时定义轻量全局方法,然后使用委托执行这些方法。
针对 MyMath.Add 方法,调用比前面两种方式复杂些:
  1. var addMethod = typeof(MyMath).GetMethod("Add");
  2. var dynamicMethod = new DynamicMethod("", typeof(int), new[] { typeof(MyMath), typeof(int), typeof(int) });
  3. //
  4. var il = dynamicMethod.GetILGenerator();
  5. il.Emit(OpCodes.Ldarg_0);
  6. il.Emit(OpCodes.Ldarg_1);
  7. il.Emit(OpCodes.Ldarg_2);
  8. il.Emit(OpCodes.Callvirt, addMethod);
  9. il.Emit(OpCodes.Ret);
  10. //
  11. var add = (Func<MyMath, int, int, int>)dynamicMethod.CreateDelegate(typeof(Func<MyMath, int, int, int>));
  12. //
  13. var math = new MyMath();
  14. var result = add(math, 1, 2);
复制代码
从第 5 行起,使用几个 IL 汇编指令,简单一说:
反射发出是在汇编级别的,很底层,也就意味着效率更高、威力更强大。反射发出能绕过跳过 JIT 可见性检查,访问 private 成员(对于 DynamicMethod 类,请查看:DynamicMethod 构造函数 (String, Type, Type[], Boolean))。
下面是几种方法的性能测试。
性能对比测试
这里对直接、反射发出、dynamic 、表达式树编译、反射五种调用方式进行性能对比测试。
测试结果
先给出测试的结果:
从上图中可以看出:
另外说明两点:
测试代码
以下是测试用代码,仅参考:
  1. using System;
  2. using System.Linq.Expressions;
  3. using System.Reflection.Emit;

  4. public class MyMath {
  5.     public int Add(int a, int b) { return a + b; }
  6. }

  7. class Program {
  8.     static void Main(string[] args) {
  9.         int result;
  10.         var math = new MyMath();
  11.         var count = 1000000;

  12.         Console.WriteLine("数据量:" + count);
  13.         Console.WriteLine("-----------------------------r\n");

  14.         using (Profiler.Step("循环:{0} ms")) {
  15.             for (int i = 0; i < count; i++)
  16.                 result = 1;
  17.         }
  18.         using (Profiler.Step("直接调用 :{0} ms")) {
  19.             for (int i = 0; i < count; i++)
  20.                 result = math.Add(i, i);
  21.         }
  22.         using (Profiler.Step("反射发出:{0} ms")) {
  23.             var emitAdd = BuildEmitAddFunc();
  24.             for (int i = 0; i < count; i++)
  25.                 result = emitAdd(math, i, i);
  26.         }
  27.         using (Profiler.Step("表达式树:{0} ms")) {
  28.             var expressionAdd = BuildExpressionAddFunc();
  29.             for (int i = 0; i < count; i++)
  30.                 result = expressionAdd(math, i, i);
  31.         }
  32.         using (Profiler.Step("dynamic 调用:{0} ms")) {
  33.             dynamic d = math;
  34.             for (int i = 0; i < count; i++)
  35.                 result = d.Add(i, i);
  36.         }
  37.         using (Profiler.Step("反射调用:{0} ms")) {
  38.             var add = typeof(MyMath).GetMethod("Add");
  39.             for (int i = 0; i < count; i++)
  40.                 result = (int)add.Invoke(math, new object[] { i, i });
  41.         }

  42.         Console.WriteLine("\r\n\r\n测试完成,任意键退出...");
  43.         Console.ReadKey();
  44.     }
  45.     static Func<MyMath, int, int, int> BuildExpressionAddFunc() {
  46.         var add = typeof(MyMath).GetMethod("Add");
  47.         var math = Expression.Parameter(typeof(MyMath));
  48.         var a = Expression.Parameter(typeof(int), "a");
  49.         var b = Expression.Parameter(typeof(int), "b");
  50.         var body = Expression.Call(math, add, a, b);
  51.         var lambda = Expression.Lambda<Func<MyMath, int, int, int>>(body, math, a, b);
  52.         return lambda.Compile();
  53.     }
  54.     static Func<MyMath, int, int, int> BuildEmitAddFunc() {
  55.         var add = typeof(MyMath).GetMethod("Add");
  56.         var dynamicMethod = new DynamicMethod("", typeof(int), new[] { typeof(MyMath), typeof(int), typeof(int) });
  57.         var il = dynamicMethod.GetILGenerator();
  58.         il.Emit(OpCodes.Ldarg_0);
  59.         il.Emit(OpCodes.Ldarg_1);
  60.         il.Emit(OpCodes.Ldarg_2);
  61.         il.Emit(OpCodes.Callvirt, add);
  62.         il.Emit(OpCodes.Ret);
  63.         return (Func<MyMath, int, int, int>)dynamicMethod.CreateDelegate(typeof(Func<MyMath, int, int, int>));
  64.     }
  65. }
复制代码
Profiler 是我写的一个类,用于简化测试:
  1. using System;
  2. using System.Diagnostics;

  3. public class Profiler : IDisposable {
  4.     private Stopwatch watch;
  5.     private string message;

  6.     private Profiler(string message) {
  7.         this.watch = new Stopwatch();
  8.         this.watch.Start();
  9.         this.message = message;
  10.     }

  11.     public void Dispose() {
  12.         watch.Stop();
  13.         Console.WriteLine(message, watch.ElapsedMilliseconds);
  14.         Console.WriteLine();
  15.     }

  16.     public static IDisposable Step(string message) {
  17.         return new Profiler(message);
  18.     }

  19.     public static T Inline<T>(string message, Func<T> func) {
  20.         using (new Profiler(message))
  21.             return func();
  22.     }
  23. }
复制代码
总结
综上所述,我们可以使用 .NET 2.0 的 DynamicMethod,.NET 3.5 引入的 Expression Tree、.NET 4 新增的 dynamic 来替换反射调用,带来更好的性能。
希望本文对大家有所帮助,也希望整天忙于项目、疲于工作的朋友抽点时间学习下 .NET Framework 的一些“新”特性。






欢迎光临 51Testing软件测试论坛 (http://bbs.51testing.com/) Powered by Discuz! X3.2