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

https://blog.csdn.net/WPwalter/article/details/83744781

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

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

本文内容
反射各方法的性能数据
反射的高性能开发建议
创建类型的实例
反射获取 Attribute
反射调用公共 / 私有方法
使用预编译框架
附本文性能测试所用的代码
所有反射相关方法
IsDefined 和 GetCustomAttribute 的专项比较
参考资料
反射各方法的性能数据
我使用 BenchmarkDotNet 基准性能测试来评估反射各个方法的性能。测试的程序基于 .NET Core 2.1 开发。

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

å¨è¿éæå¥å¾çæè¿°

▲ 各反射不同方法的运行基准测试结果

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

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

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

å¨è¿éæå¥å¾çæè¿°

那个 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 中的一些属性,例如 Assembly 和 Attributes 也是比较“耗时”的;当然,这是纳秒级别,你可以将它忽略。

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

å¨è¿éæå¥å¾çæè¿°

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

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

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

 

反射的高性能开发建议
创建类型的实例
如果你能访问到类型:

建议直接使用 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.

 

 

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值