.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> —— 自动生成多个类型的泛型 - 吕毅 一文中写了一个泛型生成器,可以稍加修改以便适应这种泛型类。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。