数码控科技猎奇Iphone动漫星座游戏电竞lolcosplay王者荣耀攻略allcnewsBLOGNEWSBLOGASKBLOGBLOGZSK全部技术问答问答技术问答it问答代码软件新闻开发博客电脑/网络手机/数码笔记本电脑互联网操作系统软件硬件编程开发360产品资源分享电脑知识文档中心IT全部全部分类全部分类技术牛文全部分类教程最新网页制作cms教程平面设计媒体动画操作系统网站运营网络安全服务器教程数据库工具网络安全软件教学vbscript正则表达式javascript批处理更多»编程更新教程更新游戏更新allitnewsJava新闻网络医疗信息化安全创业站长电商科技访谈域名会议专栏创业动态融资创投创业学院 / 产品经理创业公司人物访谈营销开发数据库服务器系统虚拟化云计算嵌入式移动开发作业作业1常见软件all电脑网络手机数码生活游戏体育运动明星影音休闲爱好文化艺术社会民生教育科学医疗健康金融管理情感社交地区其他电脑互联网软件硬件编程开发360相关产品手机平板其他电子产品摄影器材360硬件通讯智能设备购物时尚生活常识美容塑身服装服饰出行旅游交通汽车购房置业家居装修美食烹饪单机电脑游戏网页游戏电视游戏桌游棋牌游戏手机游戏小游戏掌机游戏客户端游戏集体游戏其他游戏体育赛事篮球足球其他运动球类运动赛车健身运动运动用品影视娱乐人物音乐动漫摄影摄像收藏宠物幽默搞笑起名花鸟鱼虫茶艺彩票星座占卜书画美术舞蹈小说图书器乐声乐小品相声戏剧戏曲手工艺品历史话题时事政治就业职场军事国防节日风俗法律法规宗教礼仪礼节自然灾害360维权社会人物升学入学人文社科外语资格考试公务员留学出国家庭教育学习方法语文物理生物工程学农业数学化学健康知识心理健康孕育早教内科外科妇产科儿科皮肤科五官科男科整形中医药品传染科其他疾病医院两性肿瘤科创业投资企业管理财务税务银行股票金融理财基金债券保险贸易商务文书国民经济爱情婚姻家庭烦恼北京上海重庆天津黑龙江吉林辽宁河北内蒙古山西陕西宁夏甘肃青海新疆西藏四川贵州云南河南湖北湖南山东江苏浙江安徽江西福建广东广西海南香港澳门台湾海外地区

.NET Core/Framework如何创建委托大幅度提高反射调用的性能详解

来源:脚本之家  责任编辑:小易  

前言

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

反射是一种很重要的技术,然而它与直接调用相比性能要慢很多,因此如何优化反射性能也就成为一个不得不面对的问题。 目前最常见的优化反射性能的方法就是采用委托:用委托的方式调用需要反射调用的方法(或者属性、字段)。

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

性能对比数据


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

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

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

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

using System;
using System.Diagnostics;
using System.Reflection;

namespace Walterlv.Demo
{
 public class Program
 {
 static void Main(string[] args)
 {
  // 调用的目标实例。
  var instance = new StubClass();

  // 使用反射找到的方法。
  var method = typeof(StubClass).GetMethod(nameof(StubClass.Test), new[] { typeof(int) });
  Assert.IsNotNull(method);

  // 将反射找到的方法创建一个委托。
  var func = InstanceMethodBuilder<int, int>.CreateInstanceMethod(instance, method);

  // 跟被测方法功能一样的纯委托。
  Func<int, int> pureFunc = value => value;

  // 测试次数。
  var count = 10000000;

  // 直接调用。
  var watch = new Stopwatch();
  watch.Start();
  for (var i = 0; i < count; i++)
  {
  var result = instance.Test(5);
  }

  watch.Stop();
  Console.WriteLine($"{watch.Elapsed} - {count} 次 - 直接调用");

  // 使用同样功能的 Func 调用。
  watch.Restart();
  for (var i = 0; i < count; i++)
  {
  var result = pureFunc(5);
  }

  watch.Stop();
  Console.WriteLine($"{watch.Elapsed} - {count} 次 - 使用同样功能的 Func 调用");

  // 使用反射创建出来的委托调用。
  watch.Restart();
  for (var i = 0; i < count; i++)
  {
  var result = func(5);
  }

  watch.Stop();
  Console.WriteLine($"{watch.Elapsed} - {count} 次 - 使用反射创建出来的委托调用");

  // 使用反射得到的方法缓存调用。
  watch.Restart();
  for (var i = 0; i < count; i++)
  {
  var result = method.Invoke(instance, new object[] { 5 });
  }

  watch.Stop();
  Console.WriteLine($"{watch.Elapsed} - {count} 次 - 使用反射得到的方法缓存调用");

  // 直接使用反射调用。
  watch.Restart();
  for (var i = 0; i < count; i++)
  {
  var result = typeof(StubClass).GetMethod(nameof(StubClass.Test), new[] { typeof(int) })
   ?.Invoke(instance, new object[] { 5 });
  }

  watch.Stop();
  Console.WriteLine($"{watch.Elapsed} - {count} 次 - 直接使用反射调用");
 }

 private class StubClass
 {
  public int Test(int i)
  {
  return i;
  }
 }
 }
}

如何实现

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

此方法有两个重载:

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

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

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

private class StubClass
{
 public int Test(int i)
 {
 return i;
 }
}

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

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

using System;
using System.Linq;
using System.Reflection;
using System.Diagnostics.Contracts;

namespace Walterlv.Demo
{
 public static class InstanceMethodBuilder<T, TReturnValue>
 {
 /// <summary>
 /// 调用时就像 var result = func(t)。
 /// </summary>
 [Pure]
 public static Func<T, TReturnValue> CreateInstanceMethod<TInstanceType>(TInstanceType instance, MethodInfo method)
 {
  if (instance == null) throw new ArgumentNullException(nameof(instance));
  if (method == null) throw new ArgumentNullException(nameof(method));

  return (Func<T, TReturnValue>) method.CreateDelegate(typeof(Func<T, TReturnValue>), instance);
 }

 /// <summary>
 /// 调用时就像 var result = func(this, t)。
 /// </summary>
 [Pure]
 public static Func<TInstanceType, T, TReturnValue> CreateMethod<TInstanceType>(MethodInfo method)
 {
  if (method == null)
  throw new ArgumentNullException(nameof(method));

  return (Func<TInstanceType, T, TReturnValue>) method.CreateDelegate(typeof(Func<TInstanceType, T, TReturnValue>));
 }
 }
}

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

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

您可能感兴趣的文章:


  • 本文相关:
  • 浅谈.net反射机制的性能优化 附实例下载
  • .net中 关于反射的详细介绍
  • asp.net 反射减少代码书写量
  • asp.net反射简单应用实例
  • .net/c#利用反射调用含ref或out参数的方法示例代码
  • .net/c#如何使用反射注册事件详解
  • asp.net reporting service在web application中的应用
  • asp.net中阻止页面按钮多次提交的解决办法
  • c# 可空类型分析
  • visual studio 2013+opencv2.4.10环境搭建教程
  • .net下二进制形式的文件(图片)的存储与读取详细解析
  • asp.net 上传下载输出二进制流实现代码
  • c#中的fileupload 选择后的预览效果具体实现
  • asp.net(c#)用类的思想实现插入数据到access例子
  • asp.net页面master页面与ascx用户控件传值的问题
  • aspnetpager分页控件定义及应用样式示例介绍
  • 免责声明 - 关于我们 - 联系我们 - 广告联系 - 友情链接 - 帮助中心 - 频道导航
    Copyright © 2017 www.zgxue.com All Rights Reserved