C#反射,性能优化,不止于优化

 架构师的价值,在于独立且理性的思考

e6ba15e9911ae79b96dc1c9d41ad0516.jpeg

想要写出灵活而且具有更好适应性的代码,反射是首选方案。

反射赋予程序在运行时动态创建实例的能力,可以在程序运行时(而非编译时)获取实例类型,获取元数据信息,动态调用实例方法及属性,实现在通常编程逻辑中无法完成的功能,是编程体系中的高阶技能。

反射的一大弊端是性能偏低,但反射性能究竟低多少,想必并非每个开发人员都了解,那么本着严谨求实的精神,我们来分析一下反射的执行效率及其优化方案。

01

优化方案

首先说一下反射的优化方案,反射性能优化有以下几种方案:

  1. 委托

  2. ILEmit(直接编写IL,复杂度较高)

  3. 表达式树(Expression,复杂度相对较高)

  4. 元数据缓存

02

性能测试

接下来,通过对一个属性的读写操作,分析这几种方案的性能。

下图是分别采用:属性、委托、ILEmit、表达式树、元数据缓存、反射,六种方案,对一个属性进行读写操作的性能测试结果。

22d36832294943f8a56bc73ab8331df6.png         

从测试结果可以看出:

通过反射读取属性值,与直接通过属性读取,耗时相差283倍

通过反射写入属性值,与直接通过属性写入,耗时相差77倍

相对来说,反射的性能,确实差了不少。

再从绝对耗时角度看,测试结果中,操作耗时的单位是ns(纳秒),纳秒和秒的换算关系如下:

1s 
= 1000 ms(毫秒)
= 1000000 us(微秒)
= 1000000000 ns(纳秒)

取耗时最长的106 ns,相当于0.000000106 s,0.000106 ms,也就是说,即使在一个业务逻辑中,调用10000次反射设置属性操作,总耗时也只有1.06 ms,通常一个逻辑操作耗时低于10 ms时,性能优化的必要性不大(一家之言,仅供参考)。

对于性能的追求,是每个技术人应当铭记于心的准则,可以根据自己的技术能力,业务场景以及任务排期,选择采用不同的优化方案。

03


方案分析

分析一下这几种方案:

  1. 委托

对比优化方案可知,委托的性能最好,读性能提高42倍,写性能提高41倍。

委托的实现难度较低,并且代码可阅读性较好,是首选优化方案。

注意:这里的委托是指delegate(包括:Action,Func),而不是Delegate,直接使用Delegate的性能比反射还要低2倍,需要将Delegate转换为特定类型的delegate才能起到性能提升的作用。由此也带来了委托的缺点,通用性不强,需要根据不同类型,创建相应的委托。

  1. ILEmit

ILEmit相当于直接通过IL编写代码,创建过程相对复杂,代码可读性低,编写难度高,有兴趣的可以研究一下这个库Sigil。

  1. 表达式树

直接编写表达式树也有一定难度,但相对于ILEmit要简单很多,可以深入了解一下。对于属性的读写操作,可以参考FastProperty。

  1. 元数据缓存

元数据缓存是最简单的方案,在没有精力采用其他方案时,是个不错的选择,能得到30%的性能提升。

重要说明:此性能测试耗时只包含了不同方案下,属性读写操作方法的耗时,而不包含生成委托、构造ILEmit、构造表达式树的时间,如果算上这部分时间,那么这几种方案比直接使用PropertyInfo还要慢一倍,这一点一定要注意。这就给我们的优化方案提出了新的挑战,需要提前构建委托、Emit或表达式树,并进行缓存,以便后续操作使用,这样才能达到性能优化的目的

04


总结

我们常说:手里拿个锤子,看什么都像钉子。

究其原因是因为我们手里只有锤子,了解更多的方案,扩大自己的知识边界,让自己的工具箱中多几件工具,那么面对不同问题场景时,便可以多几种可选的应对方案。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
反射C# 中一个非常强大的功能,它可以让我们在运行时动态地获取和操作对象的信息。但是由于反射需要在运行时动态解析类型和成员信息,因此会带来一定的性能损失。在需要频繁使用反射的场景下,我们需要考虑一些反射性能优化的方法。 1. 尽可能缓存反射信息 在使用反射时,我们可以将获取到的类型、方法、属性等信息缓存起来,避免重复获取。比如,我们可以使用静态变量或单例来缓存类型信息,使用委托来缓存方法调用等。这样可以避免频繁的反射操作,提高程序的性能。 2. 使用泛型方法替代反射调用 在一些场景下,我们可以使用泛型方法来替代反射调用。比如,当我们需要动态地调用某个对象的方法时,可以使用泛型方法来实现: ```csharp public static T InvokeMethod<T>(object obj, string methodName, params object[] args) { Type type = obj.GetType(); MethodInfo method = type.GetMethod(methodName); return (T)method.Invoke(obj, args); } ``` 这样就可以避免使用反射调用方法,提高程序的性能。 3. 使用 Emit 动态生成 IL 代码 在一些极端场景下,反射的性能问题可能会非常严重。这时,我们可以使用 Emit 动态生成 IL 代码来实现某些操作,比如动态生成一个类型、动态生成一个方法等。这虽然比较复杂,但可以极大地提高程序的性能。 总之,在使用反射时,我们需要尽可能地避免重复的反射操作,使用缓存和泛型方法等方法来提高程序的性能。在一些极端场景下,可以考虑使用 Emit 动态生成 IL 代码来实现更高效的操作。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值