都知道反射伤性能,但不得不反射的时候又怎么办呢?当真的被问题逼迫的时候还是能找到解决办法的。

为反射得到的方法创建一个委托,此后调用此委托将能够提高近乎直接调用方法本身的性能。(当然 Emit 也能够帮助我们显著提升性能,不过直接得到可以调用的委托不是更加方便吗?)


性能对比数据


▲ 没有什么能够比数据更有说服力(注意后面两行是有秒数的)

可能我还需要解释一下那五行数据的含义:

  1. 直接调用(��应该没有什么比直接调用函数本身更有性能优势的吧
  2. 做一个跟直接调用的方法功能一模一样的委托(��目的是看看调用委托相比调用方法本身是否有性能损失,从数据上看,损失非常小
  3. 本文重点 将反射出来的方法创建一个委托,然后调用这个委托(��看看吧,性能跟直接调差别也不大嘛
  4. 先反射得到方法,然后一直调用这个方法(��终于可以看出来反射本身还是挺伤性能的了,50 多倍的性能损失啊
  5. 缓存都不用,从头开始反射然后调用得到的方法(��100 多倍的性能损失了

以下是测试代码,可以更好地理解上图数据的含义:

  1. using System;
  2. using System.Diagnostics;
  3. using System.Reflection;
  4. namespace Walterlv.Demo
  5. {
  6. public class Program
  7. {
  8. static void Main(string[] args)
  9. {
  10. // 调用的目标实例。
  11. var instance = new StubClass();
  12. // 使用反射找到的方法。
  13. var method = typeof(StubClass).GetMethod(nameof(StubClass.Test), new[] { typeof(int) });
  14. Assert.IsNotNull(method);
  15. // 将反射找到的方法创建一个委托。
  16. var func = InstanceMethodBuilder<int, int>.CreateInstanceMethod(instance, method);
  17. // 跟被测方法功能一样的纯委托。
  18. Func<int, int> pureFunc = value => value;
  19. // 测试次数。
  20. var count = 10000000;
  21. // 直接调用。
  22. var watch = new Stopwatch();
  23. watch.Start();
  24. for (var i = 0; i < count; i++)
  25. {
  26. var result = instance.Test(5);
  27. }
  28. watch.Stop();
  29. Console.WriteLine($"{watch.Elapsed} - {count} 次 - 直接调用");
  30. // 使用同样功能的 Func 调用。
  31. watch.Restart();
  32. for (var i = 0; i < count; i++)
  33. {
  34. var result = pureFunc(5);
  35. }
  36. watch.Stop();
  37. Console.WriteLine($"{watch.Elapsed} - {count} 次 - 使用同样功能的 Func 调用");
  38. // 使用反射创建出来的委托调用。
  39. watch.Restart();
  40. for (var i = 0; i < count; i++)
  41. {
  42. var result = func(5);
  43. }
  44. watch.Stop();
  45. Console.WriteLine($"{watch.Elapsed} - {count} 次 - 使用反射创建出来的委托调用");
  46. // 使用反射得到的方法缓存调用。
  47. watch.Restart();
  48. for (var i = 0; i < count; i++)
  49. {
  50. var result = method.Invoke(instance, new object[] { 5 });
  51. }
  52. watch.Stop();
  53. Console.WriteLine($"{watch.Elapsed} - {count} 次 - 使用反射得到的方法缓存调用");
  54. // 直接使用反射调用。
  55. watch.Restart();
  56. for (var i = 0; i < count; i++)
  57. {
  58. var result = typeof(StubClass).GetMethod(nameof(StubClass.Test), new[] { typeof(int) })
  59. ?.Invoke(instance, new object[] { 5 });
  60. }
  61. watch.Stop();
  62. Console.WriteLine($"{watch.Elapsed} - {count} 次 - 直接使用反射调用");
  63. }
  64. private class StubClass
  65. {
  66. public int Test(int i)
  67. {
  68. return i;
  69. }
  70. }
  71. }
  72. }

如何实现

实现的关键就在于 MethodInfo.CreateDelegate 方法。这是 .NET Standard 中就有的方法,这意味着 .NET Framework 和 .NET Core 中都可以使用。

此方法有两个重载:

  • 要求传入一个类型,而这个类型就是应该转成的委托的类型
  • 要求传入一个类型和一个实例,一样的,类型是应该转成的委托的类型

他们的区别在于前者创建出来的委托是直接调用那个实例方法本身,后者则更原始一些,真正调用的时候还需要传入一个实例对象。

拿上面的 StubClass 来说明会更直观一些:

  1. private class StubClass
  2. {
  3. public int Test(int i)
  4. {
  5. return i;
  6. }
  7. }

前者得到的委托相当于 int Test(int i) 方法,后者得到的委托相当于 int Test(StubClass instance, int i) 方法。(在 IL 里实例的方法其实都是后者,而前者更像 C# 中的代码,容易理解。)

单独使用 CreateDelegate 方法可能每次都需要尝试第一个参数到底应该传入些什么,于是我将其封装成了泛型版本,增加易用性。

  1. using System;
  2. using System.Linq;
  3. using System.Reflection;
  4. using System.Diagnostics.Contracts;
  5. namespace Walterlv.Demo
  6. {
  7. public static class InstanceMethodBuilder<T, TReturnValue>
  8. {
  9. /// <summary>
  10. /// 调用时就像 var result = func(t)。
  11. /// </summary>
  12. [Pure]
  13. public static Func<T, TReturnValue> CreateInstanceMethod<TInstanceType>(TInstanceType instance, MethodInfo method)
  14. {
  15. if (instance == null) throw new ArgumentNullException(nameof(instance));
  16. if (method == null) throw new ArgumentNullException(nameof(method));
  17. return (Func<T, TReturnValue>) method.CreateDelegate(typeof(Func<T, TReturnValue>), instance);
  18. }
  19. /// <summary>
  20. /// 调用时就像 var result = func(this, t)。
  21. /// </summary>
  22. [Pure]
  23. public static Func<TInstanceType, T, TReturnValue> CreateMethod<TInstanceType>(MethodInfo method)
  24. {
  25. if (method == null)
  26. throw new ArgumentNullException(nameof(method));
  27. return (Func<TInstanceType, T, TReturnValue>) method.CreateDelegate(typeof(Func<TInstanceType, T, TReturnValue>));
  28. }
  29. }
  30. }

泛型的多参数版本可以使用泛型类型生成器生成,我在 生成代码,从 <T><T1, T2, Tn> —— 自动生成多个类型的泛型 - 吕毅 一文中写了一个泛型生成器,可以稍加修改以便适应这种泛型类。

.NET Core/Framework 创建委托以大幅度提高反射调用的性能的更多相关文章

  1. 动态的创建Class对象方法及调用方式性能分析

    有了Class对象,能做什么? 创建类的对象:调用Class对象的newInstance()方法 类必须有一个无参数的构造器. 类的构造器的访问权限需要足够. 思考?没有无参的构造器就不能创建对象吗? ...

  2. .NET/C# 反射的的性能数据,以及高性能开发建议(反射获取 Attribute 和反射调用方法)——转载

    原文链接:https://blog.walterlv.com/post/dotnet-high-performance-reflection-suggestions.html ***** 大家都说反射 ...

  3. 原 .NET/C# 反射的的性能数据,以及高性能开发建议(反射获取 Attribute 和反射调用方法)

    大家都说反射耗性能,但是到底有多耗性能,哪些反射方法更耗性能:这些问题却没有统一的描述. 本文将用数据说明反射各个方法和替代方法的性能差异,并提供一些反射代码的编写建议.为了解决反射的性能问题,你可以 ...

  4. C# 反射调用私有事件

    原文:C# 反射调用私有事件 在 C# 反射调用私有事件经常会不知道如何写,本文告诉大家如何调用 假设有 A 类的代码定义了一个私有的事件 class A { private event EventH ...

  5. 2019-11-29-C#-反射调用私有事件

    原文:2019-11-29-C#-反射调用私有事件 title author date CreateTime categories C# 反射调用私有事件 lindexi 2019-11-29 08: ...

  6. 2019-8-30-C#-反射调用私有事件

    title author date CreateTime categories C# 反射调用私有事件 lindexi 2019-08-30 08:52:57 +0800 2018-09-19 20: ...

  7. 在C++中反射调用.NET(三)

    在.NET与C++之间传输集合数据 上一篇<在C++中反射调用.NET(二)>中,我们尝试了反射调用一个返回DTO对象的.NET方法,今天来看看如何在.NET与C++之间传输集合数据. 使 ...

  8. 反射调用与Lambda表达式调用

    想调用一个方法很容易,直接代码调用就行,这人人都会.其次呢,还可以使用反射.不过通过反射调用的性能会远远低于直接调用——至少从绝对时间上来看的确是这样.虽然这是个众所周知的现象,我们还是来写个程序来验 ...

  9. 深入分析Java反射(八)-优化反射调用性能

    Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行Deb ...

随机推荐

  1. Outlook 配置qq邮箱账号

    最近想用Outlook 2013管理QQ邮件,配置好久都没有成功,结果最后发现第三方登陆QQ邮箱不使用QQ密码,而是使用一个叫”授权码”的东西.(用户名自动生成的,授权码就填这,报错后填会测试不通过) ...

  2. Memcached stats items 命令

    Memcached stats items 命令用于显示各个 slab 中 item 的数目和存储时长(最后一次访问距离现在的秒数). 语法: stats items 命令的基本语法格式如下: sta ...

  3. 【Linux】结合Python 简易实现监控公司网站,邮件发送异常

    背景 由于一些原因,博主负责测试的网站的服务器切换到了香港,切换后出现了多次访问超时的情况 于是主动请缨写一个自动监测的脚本,本来准备完全使用shell来写,后来发现shell发送邮件只能在测试机之间 ...

  4. JSP 异常处理

    JSP 异常处理 当编写JSP程序的时候,程序员可能会遗漏一些BUG,这些BUG可能会出现在程序的任何地方.JSP代码中通常有以下几类异常: 检查型异常:检查型异常就是一个典型的用户错误或者一个程序员 ...

  5. 10.彻底理解ReentrantLock

    1. ReentrantLock的介绍 ReentrantLock重入锁,是实现Lock接口的一个类,也是在实际编程中使用频率很高的一个锁,支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该 ...

  6. simple HTTP server with upload

    #!/usr/bin/env python """Simple HTTP Server With Upload. https://github.com/tualatrix ...

  7. PHP---初探PHP

    初探PHP 虽然说前后端分离,但作为前端还是要跟数据打交道的,所以对后台语言的了解还是很有必要的.今天要学的就是PHP. 什么是PHP? PHP(外文名:PHP: Hypertext Preproce ...

  8. 常用js、jquery 语句(句型)

    1.动态更改设置属性(class  style 都是属性) $("#sendPhoneNum").attr("class", "n_input3&qu ...

  9. 012PHP基础知识——运算符(五)

    <?php /** * 运算符的短路: * && 逻辑与 || 逻辑或 存在短路: */ /* $a = 1; $a==1 ||$c=100; //逻辑或:第一个表达式返回tru ...

  10. 【zzuli-2266】number(二进制处理)

    题目描述 某人刚学习了数位DP,他在某天忽然思考如下问题: 给定n,问有多少数对<x, y>满足: x, y∈[1, n], x < y x, y中出现的[0, 9]的数码种类相同 ...