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

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

大家都说反射耗性能,但是到底有多耗性能,哪些反射方法更耗性能;这些问题却没有统一的描述。

本文将用数据说明反射各个方法和替代方法的性能差异,并提供一些反射代码的编写建议。为了解决反射的性能问题,你可以遵循本文采用的各种方案。


本文内容

      • 反射各方法的性能数据
      • 反射的高性能开发建议
        • 创建类型的实例
        • 反射获取 Attribute
        • 反射调用公共 / 私有方法
        • 使用预编译框架
      • 附本文性能测试所用的代码
        • 所有反射相关方法
        • IsDefined 和 GetCustomAttribute 的专项比较
        • 参考资料

反射各方法的性能数据

我使用 BenchmarkDotNet 基准性能测试来评估反射各个方法的性能。测试的程序基于 .NET Core 2.1 开发。

先直观地贴出我的运行结果:

在这里插入图片描述
▲ 各反射不同方法的运行基准测试结果

我把上面的表格复制下来成为文字,这样你也可以拿走我的这部分数据:

MethodMeanErrorStdDevMedian
Assembly13.5315 ns0.3004 ns0.4764 ns13.4878 ns
Attributes7.0893 ns0.1248 ns0.1168 ns7.0982 ns
CustomAttributes1,489.1654 ns29.4428 ns27.5408 ns1,482.5038 ns
GetCustomAttributesData1,514.5503 ns29.6863 ns39.6303 ns1,507.2949 ns
GetCustomAttributes1,171.8969 ns22.5305 ns27.6695 ns1,167.2777 ns
GetCustomAttribute1,139.8609 ns22.8043 ns24.4003 ns1,140.5437 ns
GetCustomAttribute_Generic1,115.0049 ns13.1929 ns11.6952 ns1,111.4426 ns
GetCustomAttributes_Generic1,164.5132 ns22.7775 ns24.3716 ns1,165.2747 ns
New0.0003 ns0.0013 ns0.0012 ns0.0000 ns
Lambda0.0063 ns0.0149 ns0.0139 ns0.0000 ns
Activator_CreateInstance48.8633 ns0.6300 ns0.5893 ns48.8906 ns
Activator_CreateInstance_Generic47.7029 ns0.9649 ns1.0724 ns47.5851 ns
Expression_New75,634.4035 ns1,467.3285 ns1,372.5400 ns75,413.2837 ns
CachedExpression_New7.8923 ns0.1988 ns0.4105 ns7.7004 ns

如果你希望了解以上每一项的意思,可以通过阅读本文文末的代码来了解其实现。基本上名称就代表着反射调用相同的方法。

你一定会说这张表不容易看出性能差距。那么我一定会放图:

在这里插入图片描述

那个 Expression_New 在图中独树一帜,远远把其他方法甩在了后面。那是个什么方法?

那是在使用 Expression 表达式创建一个类型的新实例:

var @new = Expression.New(typeof(ReflectionTarget));
var lambda = Expression.Lambda<Func<ReflectionTarget>>(@new).Compile();
var instance = lambda.Invoke();

也就是说,如果你只是希望创建一个类型的新实例,就不要考虑使用 Expression.New 的方式了。除非此方法将执行非常多次,而你把那个 lambda 表达式缓存下来了。这对应着图表中的 CachedExpression_New

其他的现在都看不出来性能差异,于是我们把耗时最长的 Expression_New 一项去掉:

在这里插入图片描述

我们立刻可以从图中得到第二梯队的性能巨头 —— 就是 CustomAttributes 系列。我使用了多种不同的 CustomAttribute 获取方法,得到的结果差异不大,都“比较耗时”。不过在这些耗时的方法里面找到不那么耗时的,就是 Type 的扩展方法系列 GetCustomAttribute 了,比原生非扩展方法的性能稍好。

不过其他的性能差异又被淹没了。于是我们把 CustomAttributes 系列也删掉:

在这里插入图片描述

于是我们又得到了第三梯队的性能大头 —— Activator.CreateInstance 系列。而是否调用泛型方法的耗时差异不大。

然后,我们把 Activator.CreateInstance 也干掉,可以得到剩下其他的性能消耗。

在这里插入图片描述

也就是说,只是获取 Type 中的一些属性,例如 AssemblyAttributes 也是比较“耗时”的;当然,这是纳秒级别,你可以将它忽略。

要不要试试把第四梯队的也干掉呢?于是你可以得到 newLambda 的差异:

在这里插入图片描述

原本在上面所有图中看起来都没有时间的 newLambda 竟然差异如此巨大;不过,这都是千分之一纳秒级别了;如果你创建的类数量不是百万级别以上,你还真的可以忽略。

new 指的是 new Foo()Lambda 指的是 var func = () => new Foo(); func();

对于 GetCustomAttribute,还有另一个方法值得注意:IsDefined;可以用来判断是否定义了某个特定的 Attribute

var isDefined = _targetType.IsDefined(typeof(ReflectionTargetAttribute), false);
if (isDefined)
{
    var attribute = _targetType.GetCustomAttribute<ReflectionTargetAttribute>();
}

而这个方法与 GetCustomAttribute 的性能差距也有些大:

MethodMeanErrorStdDevRatioRatioSD
IsDefined653.8 ns13.07 ns16.53 ns1.000.00
GetCustomAttribute1,149.6 ns22.97 ns22.56 ns1.760.06
GetGenericCustomAttribute1,216.5 ns24.15 ns54.51 ns1.810.07

咋看之下似乎与 GetCustomAttribute 方法重复,而且如果先判断再获取,可能总时间更长。不过这种方法就是适用于一次性对大量类型进行判断,如果只有少量类型定义了某种 Attribute,那么提前使用 IsDefined 判断可以获得总体更加的性能。

反射的高性能开发建议

创建类型的实例

如果你能访问到类型:

  • 建议直接使用 new,性能最好。
  • 如果不希望直接 new 出来,可以考虑使用 Func 或者 Lazy 创建。这时会多消耗一些性能,不过基数小,增量不大。

如果你不能访问到类型:

  • 如果只能从 Type 创建,则使用 Activator.CreateInstance 系列。
  • 如果你使用其他方式创建,请一定使用缓存。

除了使用 Expression 创建,你还可以使用 Emit 创建,不过这也要求能够访问到类型:

  • 使用 Emit 生成 IL 代码 - 吕毅

对于缓存,可以参考:

  • .NET Core/Framework 创建委托以大幅度提高反射调用的性能 - 吕毅
  • .NET/C# 推荐一个我设计的缓存类型(适合缓存反射等耗性能的操作,附用法) - 吕毅

对于创建对象更多的性能数据,可以参考:

  • C# 直接创建多个类和使用反射创建类的性能 - 林德熙
  • C# 性能分析 反射 VS 配置文件 VS 预编译 - 林德熙

反射获取 Attribute

获取 Attribute 也是耗时的操作。

  • 如果你只是获取极少数类型的 Attribute,建议直接调用 GetCustomAttribute 扩展方法。
  • 如果你需要判断大量类型的 Attribute,建议先使用 IsDefined 判断是否存在,如果存在才使用 GetCustomAttribute 方法获取真实实例。

反射调用公共 / 私有方法

反射调用方法与构造方法几乎是一样的,不同之处就在于公共方法可以创建出委托缓存,而私有方法却不行。

有了委托缓存,你只有第一次才需要真的调用反射,后续可以使用缓存的委托或 Lambda 表达式;而私有方法是无法创建的,你每次都需要通过反射来调用相关方法。

关于私有方法的反射:

  • C# 使用反射获取私有属性的方法
  • C# 反射调用私有事件

关于缓存:

  • .NET Core/Framework 创建委托以大幅度提高反射调用的性能 - 吕毅
  • .NET/C# 推荐一个我设计的缓存类型(适合缓存反射等耗性能的操作,附用法) - 吕毅

使用预编译框架

使用预编译框架,你可以在编译期间将那些耗时的反射操作编译成类似 new 和属性 get 这样的简单 CLR 调用,性能差距近乎于最开始图表中第二张图和第五张图那样,具有数千倍的差距。

  • 课程 预编译框架,开发高性能应用 - 微软技术暨生态大会 2018 - walterlv
  • dotnet-campus/SourceFusion: SourceFusion is a pre-compile framework based on Roslyn. It helps you to build high-performance .NET code.

附本文性能测试所用的代码

本文性能测试使用 BenchmarkDotNet,在 Main 函数中调用以下代码跑起来:

BenchmarkRunner.Run<Reflections>();

你可以阅读 C# 标准性能测试 - 林德熙 了解基准性能测试的基本用法,在 C# 标准性能测试高级用法 - 林德熙 中了解到更多基准测试方法的使用。

所有反射相关方法

using BenchmarkDotNet.Attributes;
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Walterlv.Demo.Reflection
{
    public class Reflections
    {
        private static readonly Type _targetType = typeof(ReflectionTarget);
        private static Func<ReflectionTarget> _cachedExpressionFunc;

        private static Func<ReflectionTarget> CachedExpressionFunc
        {
            get
            {
                if (_cachedExpressionFunc == null)
                {
                    var @new = Expression.New(typeof(ReflectionTarget));
                    var lambda = Expression.Lambda<Func<ReflectionTarget>>(@new).Compile();
                    _cachedExpressionFunc = lambda;
                }

                return _cachedExpressionFunc;
            }
        }

        [Benchmark]
        public void Assembly()
        {
            var assembly = _targetType.Assembly;
        }

        [Benchmark]
        public void Attributes()
        {
            var attributes = _targetType.Attributes;
        }

        [Benchmark]
        public void CustomAttributes()
        {
            var attribute = _targetType.CustomAttributes.FirstOrDefault(
                x => x.AttributeType == typeof(ReflectionTargetAttribute));
        }

        [Benchmark]
        public void GetCustomAttributesData()
        {
            var attribute = _targetType.GetCustomAttributesData().FirstOrDefault(
                x => x.AttributeType == typeof(ReflectionTargetAttribute));
        }

        [Benchmark]
        public void GetCustomAttributes()
        {
            var attribute = _targetType.GetCustomAttributes(typeof(ReflectionTargetAttribute), false).FirstOrDefault();
        }

        [Benchmark]
        public void GetCustomAttribute()
        {
            var attribute = _targetType.GetCustomAttribute(typeof(ReflectionTargetAttribute), false);
        }

        [Benchmark]
        public void GetCustomAttribute_Generic()
        {
            var attribute = _targetType.GetCustomAttribute<ReflectionTargetAttribute>(false);
        }

        [Benchmark]
        public void GetCustomAttributes_Generic()
        {
            var attribute = _targetType.GetCustomAttributes<ReflectionTargetAttribute>(false);
        }

        [Benchmark]
        public void New()
        {
            var instance = new ReflectionTarget();
        }

        [Benchmark]
        public void Lambda()
        {
            var instance = new ReflectionTarget();
        }

        [Benchmark]
        public void Activator_CreateInstance()
        {
            var instance = (ReflectionTarget) Activator.CreateInstance(_targetType);
        }

        [Benchmark]
        public void Activator_CreateInstance_Generic()
        {
            var instance = Activator.CreateInstance<ReflectionTarget>();
        }

        [Benchmark]
        public void Expression_New()
        {
            var @new = Expression.New(typeof(ReflectionTarget));
            var lambda = Expression.Lambda<Func<ReflectionTarget>>(@new).Compile();
            var instance = lambda.Invoke();
        }

        [Benchmark]
        public void CachedExpression_New()
        {
            var instance = CachedExpressionFunc.Invoke();
        }
    }
}

IsDefined 和 GetCustomAttribute 的专项比较

using System;
using System.Reflection;
using BenchmarkDotNet.Attributes;

namespace Walterlv.Demo.Reflection
{
    public class IsDefinedVsGetCustomAttribute
    {
        private static readonly Type _targetType = typeof(ReflectionTarget);

        [Benchmark(Baseline = true)]
        public void IsDefined()
        {
            var isDefined = _targetType.IsDefined(typeof(ReflectionTargetAttribute), false);
        }

        [Benchmark]
        public void GetCustomAttribute()
        {
            var attribute = _targetType.GetCustomAttribute(typeof(ReflectionTargetAttribute), false);
        }

        [Benchmark]
        public void GetGenericCustomAttribute()
        {
            var attribute = _targetType.GetCustomAttribute<ReflectionTargetAttribute>(false);
        }
    }
}

参考资料

  • c# - Is there a benefit of using IsDefined over GetCustomAttributes - Stack Overflow
  • Accessing Attributes by Using Reflection (C#) - Microsoft Docs
  • win10 uwp 反射
  • Reference Source
  • A Super-Fast C# Extension Method using Expression Trees to Create an instance from a Type
  • Retrieving Custom Attributes Using Reflection - Scott Dorman
  • Showtime - BenchmarkDotNet
  • Choosing RunStrategy - BenchmarkDotNet

知识共享许可协议
作品由吕毅采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 吕毅 (包含链接:https://walterlv.com),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系 (walter.lv@qq.com)。

相关文章:

  • .NET/C# 异常处理:写一个空的 try 块代码,而把重要代码写到 finally 中(Constrained Execution Regions)
  • WindowsXamlHost:在 WPF 中使用 UWP 控件库中的控件
  • WindowsXamlHost:在 WPF 中使用 UWP 的控件(Windows Community Toolkit)
  • .NET/C# 在代码中测量代码执行耗时的建议(比较系统性能计数器和系统时间)
  • 编写 Target 检测 MSBuild / dotnet build 此次编译是否是差量编译
  • 使用 Win2D 绘制带图片纹理的圆(或椭圆)
  • Win2D 中的游戏循环:CanvasAnimatedControl
  • 使用 Windows 10 中的加速度计(Accelerometer,重力传感器)
  • 用 dotTrace 进行性能分析时,各种不同性能分析选项的含义和用途
  • 如何创建一个基于 .NET Core 3 的 WPF 项目
  • 将基于 .NET Framework 的 WPF 项目迁移到基于 .NET Core 3
  • 了解 .NET 的默认 TaskScheduler 和线程池(ThreadPool)设置,避免让 Task.Run 的性能急剧降低
  • .NET 中小心嵌套等待的 Task,它可能会耗尽你线程池的现有资源,出现类似死锁的情况
  • 在有 UI 线程参与的同步锁(如 AutoResetEvent)内部使用 await 可能导致死锁
  • 不要使用 Dispatcher.Invoke,因为它可能在你的延迟初始化 LazyT 中导致死锁
  • 「面试题」如何实现一个圣杯布局?
  • Cookie 在前端中的实践
  • electron原来这么简单----打包你的react、VUE桌面应用程序
  • IDEA常用插件整理
  • PHP 的 SAPI 是个什么东西
  • Protobuf3语言指南
  • Python代码面试必读 - Data Structures and Algorithms in Python
  • rabbitmq延迟消息示例
  • RxJS 实现摩斯密码(Morse) 【内附脑图】
  • V4L2视频输入框架概述
  • Vue小说阅读器(仿追书神器)
  • 当SetTimeout遇到了字符串
  • 回顾 Swift 多平台移植进度 #2
  • 开发基于以太坊智能合约的DApp
  • 看域名解析域名安全对SEO的影响
  • 每天10道Java面试题,跟我走,offer有!
  • 七牛云 DV OV EV SSL 证书上线,限时折扣低至 6.75 折!
  • 驱动程序原理
  • 提升用户体验的利器——使用Vue-Occupy实现占位效果
  • 微服务框架lagom
  • # 数论-逆元
  • #DBA杂记1
  • #微信小程序(布局、渲染层基础知识)
  • (html转换)StringEscapeUtils类的转义与反转义方法
  • (rabbitmq的高级特性)消息可靠性
  • (每日持续更新)jdk api之FileReader基础、应用、实战
  • (免费领源码)python#django#mysql校园校园宿舍管理系统84831-计算机毕业设计项目选题推荐
  • (企业 / 公司项目)前端使用pingyin-pro将汉字转成拼音
  • (五)大数据实战——使用模板虚拟机实现hadoop集群虚拟机克隆及网络相关配置
  • (转)VC++中ondraw在什么时候调用的
  • (转)编辑寄语:因为爱心,所以美丽
  • .NET 5.0正式发布,有什么功能特性(翻译)
  • .NET CORE Aws S3 使用
  • .NET Framework杂记
  • .net使用excel的cells对象没有value方法——学习.net的Excel工作表问题
  • //解决validator验证插件多个name相同只验证第一的问题
  • ?
  • @ConfigurationProperties注解对数据的自动封装
  • @data注解_一枚 架构师 也不会用的Lombok注解,相见恨晚
  • @DependsOn:解析 Spring 中的依赖关系之艺术