.NET Core/Framework 创建委托以大幅度提高反射调用的性能
都知道反射伤性能,但不得不反射的时候又怎么办呢?当真的被问题逼迫的时候还是能找到解决办法的。
为反射得到的方法创建一个委托,此后调用此委托将能够提高近乎直接调用方法本身的性能。(当然 Emit 也能够帮助我们显著提升性能,不过直接得到可以调用的委托不是更加方便吗?)
性能对比数据
▲ 没有什么能够比数据更有说服力(注意后面两行是有秒数的)
可能我还需要解释一下那五行数据的含义:
- 直接调用(��应该没有什么比直接调用函数本身更有性能优势的吧)
- 做一个跟直接调用的方法功能一模一样的委托(��目的是看看调用委托相比调用方法本身是否有性能损失,从数据上看,损失非常小)
- 本文重点 将反射出来的方法创建一个委托,然后调用这个委托(��看看吧,性能跟直接调差别也不大嘛)
- 先反射得到方法,然后一直调用这个方法(��终于可以看出来反射本身还是挺伤性能的了,50 多倍的性能损失啊)
- 缓存都不用,从头开始反射然后调用得到的方法(��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 Core/Framework 创建委托以大幅度提高反射调用的性能的更多相关文章
- 动态的创建Class对象方法及调用方式性能分析
有了Class对象,能做什么? 创建类的对象:调用Class对象的newInstance()方法 类必须有一个无参数的构造器. 类的构造器的访问权限需要足够. 思考?没有无参的构造器就不能创建对象吗? ...
- .NET/C# 反射的的性能数据,以及高性能开发建议(反射获取 Attribute 和反射调用方法)——转载
原文链接:https://blog.walterlv.com/post/dotnet-high-performance-reflection-suggestions.html ***** 大家都说反射 ...
- 原 .NET/C# 反射的的性能数据,以及高性能开发建议(反射获取 Attribute 和反射调用方法)
大家都说反射耗性能,但是到底有多耗性能,哪些反射方法更耗性能:这些问题却没有统一的描述. 本文将用数据说明反射各个方法和替代方法的性能差异,并提供一些反射代码的编写建议.为了解决反射的性能问题,你可以 ...
- C# 反射调用私有事件
原文:C# 反射调用私有事件 在 C# 反射调用私有事件经常会不知道如何写,本文告诉大家如何调用 假设有 A 类的代码定义了一个私有的事件 class A { private event EventH ...
- 2019-11-29-C#-反射调用私有事件
原文:2019-11-29-C#-反射调用私有事件 title author date CreateTime categories C# 反射调用私有事件 lindexi 2019-11-29 08: ...
- 2019-8-30-C#-反射调用私有事件
title author date CreateTime categories C# 反射调用私有事件 lindexi 2019-08-30 08:52:57 +0800 2018-09-19 20: ...
- 在C++中反射调用.NET(三)
在.NET与C++之间传输集合数据 上一篇<在C++中反射调用.NET(二)>中,我们尝试了反射调用一个返回DTO对象的.NET方法,今天来看看如何在.NET与C++之间传输集合数据. 使 ...
- 反射调用与Lambda表达式调用
想调用一个方法很容易,直接代码调用就行,这人人都会.其次呢,还可以使用反射.不过通过反射调用的性能会远远低于直接调用——至少从绝对时间上来看的确是这样.虽然这是个众所周知的现象,我们还是来写个程序来验 ...
- 深入分析Java反射(八)-优化反射调用性能
Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行Deb ...
随机推荐
- 分页式存储管理方式AND请求分页式存储管理
先说下什么是页(页面):就是将用户的程序的的地址空间分成固定大小的区域,称为”页“,或者”页面“ 之后将这些页离散的放进内存中,这样解决了内存的碎片问题 记得老师上课说了下这两个概念不能混,现在区分下 ...
- 完美解决 No toolchains found in the NDK toolchains folder for ABI with prefix: mips64el-linux-android
问题描述 好久之前的一个Android项目,最近需要重构一下 因为Android Studio的开发环境以及Gradle的版本等等都进行了一定的更新,于是导入Project以后,出现了报错: No t ...
- flask学习(九):模板渲染和参数传递
一. 如何渲染模板 1. 模板放在templates文件夹下 2. 从flask中导入render_template函数 3. 在视图函数中,使用render_template函数,渲染模板 注意:只 ...
- Attribute 'items' must be an array, a Collection or a Map错误解决!
唉!真的要说一句话叫做论一串代码的重要性!就是如此的气人!气的牙根痒痒! 前几天刚刚写过SpringMVC之ModelAndView的 jsp值在浏览页面不显示的问题!也是因为这一串代码,但是这一次一 ...
- SpringIOC源码分析总结
大致的加载过程: spring ioc容器的加载,大体上经过以下几个过程: 资源文件定位.解析.注册.实例化 1.资源文件定位:主要发生在ApplicationContext中,由于applicati ...
- NetHogs 实时检测网络流量 转
有很多适用于Linux系统的开源网络监视工具.比如说,你可以用命令iftop来检查带宽使用情况. netstat用来查看接口统计报告,还有top监控系统当前运行进程.但是如果你想要找一个能够按进程实时 ...
- garylog学习篇
官方文档:https://www.graylog.org/ 简介 Graylog 是一个简单易用.功能较全面的日志管理工具,相比 ELK 组合, 优点: 部署维护简单查询语法简单易懂(对比ES的语法… ...
- Python之路day13 web 前端(JavaScript,DOM操作)
参考链接:http://www.cnblogs.com/wupeiqi/articles/5433893.html day13 1. CSS示例 2. JavaScript 3. DOM操作 上节内容 ...
- CF 913 A
题解: 首先判断n是否大于30 大于30输出m 否则输出m%2^n 代码: #include<bits/stdc++.h> using namespace std; int n,m; in ...
- TPCC-MySQL(转自imysql.com)
[Tpcc-mysql] Tpcc-mysql是percona基于tpcc衍生出来的产品,专用于mysql基准测试,其源码放在bazaar(Bazaar是一个分布式的版本控制系统,采用 GPL 许可协 ...