51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 1170|回复: 0
打印 上一主题 下一主题

【转】替代反射调用的几种方式及性能测试

[复制链接]
  • TA的每日心情
    郁闷
    前天 09:01
  • 签到天数: 980 天

    连续签到: 5 天

    [LV.10]测试总司令

    跳转到指定楼层
    1#
    发表于 2016-10-26 10:55:08 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    园子里和这个话题的相关文章比较多,本文是旧话重提,外加个小的总结。主要因为近期看到很多同事、朋友都已经使用 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 汇编指令,简单一说:
    • 第 5 行,OpCodes.Ldarg_0 是将索引为 0 的参数值推送到堆栈上,Ldarg_1、Ldarg_2 以此类推;
    • 第 6 行,OpCodes.Callvirt 是调用对象的(后期绑定)方法,并且将返回值推送到计算堆栈上;
    • 第 9 行,OpCodes.Ret 表达从当前方法返回,并将返回值(如果存在)从调用方的计算堆栈推送到被调用方的计算堆栈上。
    反射发出是在汇编级别的,很底层,也就意味着效率更高、威力更强大。反射发出能绕过跳过 JIT 可见性检查,访问 private 成员(对于 DynamicMethod 类,请查看:DynamicMethod 构造函数 (String, Type, Type[], Boolean))。
    下面是几种方法的性能测试。
    性能对比测试
    这里对直接、反射发出、dynamic 、表达式树编译、反射五种调用方式进行性能对比测试。
    测试结果
    先给出测试的结果:
    从上图中可以看出:
    • 直接调用性能最佳;
    • 反射发出和表达式树两种方式性能相当,速度接近直接调用;
    • dynamic 性能居中,也不错;
    • 反射方式性能最差。

    另外说明两点:
    • 本次测试仅针对 MyMath.Add 方法,其参数和返回值都是值类型,反射调用时存在大量装箱、拆箱。如果测试方法的参数和返回值都是引用类型,反射方式与其它方式间的差距会小些。
    • 从上图可以看出这几次方式性能差别较大,但此结果是重复 100 万次的情况下得出的。考虑单次调用,反射只比直接调用慢 381 纳秒。如果你的代码不是位于循环的中心或是系统的瓶颈,调用次数不多,性能差异可以完全忽略。

    测试代码
    以下是测试用代码,仅参考:
    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 的一些“新”特性。

    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏1
    回复

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /2 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-7-7 16:23 , Processed in 0.076321 second(s), 23 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表