当前位置: 首页 > news >正文

.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) });

            // 将反射找到的方法创建一个委托。
            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;
            }
        }
    }
}

以上的 InstanceMethodBuilder 在后文会介绍。

如何实现

实现的关键就在于 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> —— 自动生成多个类型的泛型 - 吕毅 一文中写了一个泛型生成器,可以稍加修改以便适应这种泛型类。

相关文章:

  • 在 Windows 安装期间将 MBR 磁盘转换为 GPT 磁盘
  • 解决大于 4GB 的 Windows 10 镜像在 UEFI 模式下的安装问题
  • 为什么 UEFI 方式启动的 U 盘必须使用 FAT32 文件系统?
  • 不再为命名而苦恼!使用 MSTestEnhancer 单元测试扩展,写契约就够了
  • Windows 10 自带那么多图标,去哪里找呢?
  • 如何删除 Windows 10 系统生成的 WindowsApps 文件夹
  • 命令“xxx.exe xxx”已退出,代码为 n。这些错误是什么意思?
  • 将 async/await 异步代码转换为安全的不会死锁的同步代码
  • 屏幕上那个灰色带有数字的框是什么?看着好难受!
  • Roslyn 入门:使用 Roslyn 静态分析现有项目中的代码
  • Roslyn 入门:使用 Visual Studio 的语法可视化窗格查看和了解代码的语法树
  • 利用 ReSharper 自定义代码中的错误模式,在代码审查之前就发现并修改错误
  • 在编写异步方法时,使用 ConfigureAwait(false) 避免使用者死锁
  • (C#)if (this == null)?你在逗我,this 怎么可能为 null!用 IL 编译和反编译看穿一切
  • 解决 mklink 使用中的各种坑(硬链接,软链接/符号链接,目录链接)
  • 【140天】尚学堂高淇Java300集视频精华笔记(86-87)
  • 【译】理解JavaScript:new 关键字
  • AHK 中 = 和 == 等比较运算符的用法
  • Android路由框架AnnoRouter:使用Java接口来定义路由跳转
  • Angular 4.x 动态创建组件
  • Angular js 常用指令ng-if、ng-class、ng-option、ng-value、ng-click是如何使用的?
  • Babel配置的不完全指南
  • CentOS 7 修改主机名
  • Cookie 在前端中的实践
  • Django 博客开发教程 16 - 统计文章阅读量
  • git 常用命令
  • October CMS - 快速入门 9 Images And Galleries
  • PHP变量
  • Swift 中的尾递归和蹦床
  • 第三十一到第三十三天:我是精明的小卖家(一)
  • 读懂package.json -- 依赖管理
  • 回顾2016
  • 基于Dubbo+ZooKeeper的分布式服务的实现
  • 记录一下第一次使用npm
  • 如何胜任知名企业的商业数据分析师?
  • 数据仓库的几种建模方法
  • 听说你叫Java(二)–Servlet请求
  • 小程序、APP Store 需要的 SSL 证书是个什么东西?
  • 写给高年级小学生看的《Bash 指南》
  • 06-01 点餐小程序前台界面搭建
  • ionic入门之数据绑定显示-1
  • #{}和${}的区别是什么 -- java面试
  • #预处理和函数的对比以及条件编译
  • (2)STM32单片机上位机
  • (十一)JAVA springboot ssm b2b2c多用户商城系统源码:服务网关Zuul高级篇
  • .Net Web窗口页属性
  • .NET 应用启用与禁用自动生成绑定重定向 (bindingRedirect),解决不同版本 dll 的依赖问题
  • @EventListener注解使用说明
  • @RequestMapping处理请求异常
  • [COI2007] Sabor
  • [J2ME]如何替换Google Map静态地图自带的Marker
  • [LeetBook]【学习日记】数组内乘积
  • [leetcode]Clone Graph
  • [LeetCode]Pow(x,n)
  • [ListView.View=List]的垂直滚动条