C#反射的的性能数据,以及⾼性能开发建议(反射获取
Attribute和反射调⽤⽅法)
⼤家都说反射耗性能,但是到底有多耗性能,哪些反射⽅法更耗性能;这些问题却没有统⼀的描述。
本⽂将⽤数据说明反射各个⽅法和替代⽅法的性能差异,并提供⼀些反射代码的编写建议。为了解决反射的性能问题,你可以遵循本⽂采⽤的各种⽅案。
本⽂内容
反射各⽅法的性能数据
反射的⾼性能开发建议
创建类型的实例
反射获取 Attribute
反射调⽤公共 / 私有⽅法
使⽤预编译框架
附本⽂性能测试所⽤的代码
所有反射相关⽅法
IsDefined 和 GetCustomAttribute 的专项⽐较
参考资料
反射各⽅法的性能数据
我使⽤ BenchmarkDotNet 基准性能测试来评估反射各个⽅法的性能。测试的程序基于 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 中的⼀些属性,例如 Asmbly 和 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 代码 - 吕毅
对于缓存,可以参考:
Core/Framework 创建委托以⼤幅度提⾼反射调⽤的性能 - 吕毅
/C# 推荐⼀个我设计的缓存类型(适合缓存反射等耗性能的操作,附⽤法) - 吕毅
对于创建对象更多的性能数据,可以参考:
C# 直接创建多个类和使⽤反射创建类的性能 - 林德熙
金工实习C# 性能分析 反射 VS 配置⽂件 VS 预编译 - 林德熙
巴西地形反射获取 Attribute
获取 Attribute 也是耗时的操作。
如果你只是获取极少数类型的 Attribute,建议直接调⽤ GetCustomAttribute 扩展⽅法。
如果你需要判断⼤量类型的 Attribute,建议先使⽤ IsDefined 判断是否存在,如果存在才使⽤ GetCustomAttribute ⽅法获取真实实例。
反射调⽤公共 / 私有⽅法张正甫
反射调⽤⽅法与构造⽅法⼏乎是⼀样的,不同之处就在于公共⽅法可以创建出委托缓存,⽽私有⽅法却不⾏。
有了委托缓存,你只有第⼀次才需要真的调⽤反射,后续可以使⽤缓存的委托或 Lambda 表达式;⽽私有⽅法是⽆法创建的,你每次都需要通过反射来调⽤相关⽅法。
关于私有⽅法的反射:
C# 使⽤反射获取私有属性的⽅法
C# 反射调⽤私有事件
关于缓存: