在前二篇博客中,我分别介绍了二种优化反射的方法:
1. Delegate:委托。
2. CodeDOM:动态代码生成。
这是二种截然不同的方法,性能的差距也很大。
今天的博客将着重比较它们的优缺点,以及给出它们的使用建议。

用Delegate优化反射的缺点

在评价委托方案时,我认为有必要细分一下委托方案:
1. 强类型委托,例如:Action<TTarget, TValue>
2. 弱类型委托,例如:Action<object, object>

它们的优点分别是:
强类型委托:速度快,已经最接近直接调用的性能,然而它的缺点是 不通用。
弱类型委托:比较通用,且经过一些代码封装后,使用方便,但是 封装后的性能会变差。

用Delegate优化反射的优点

优点有二个:
1. 实现简单,不管是使用Emit, ExpressionTree还是CreateDelegate,代码量都不大。
2. 方法通用,使用弱类型委托,我们可以封装出很容易使用的API,且适用于任何项目。

用CodeDOM优化反射的优点

最大的,也是唯一的优点就是:性能好。
由于生成的是直接调用的代码,因此最终运行的是直接调用的代码,所以没有性能损耗。
另外,代码生成器可以决定最终生成的代码质量,代码生成器越优秀,代码的性能也会更优秀。

注意:当使用这种技术时,不同人可能会有不同的使用方法,最终可以得到性能不同的结果, (理论上)最坏情况下可能比委托还差。

如果希望借助这种优化方式实现最好的性能,需要做好二件事情:
1. 保证最终生成的代码质量是最优的。
2. 编译方式的设计要合理(用好CodeDOM)。

如何保证最终生成的代码质量是最优的,我给不了建议,需要您自己去思考,
我们接着讨论第2点。

如何用好CodeDOM?

虽然采用动态编译技术,我们可以生成直接调用的代码来代替反射调用,这样就不会有任何性能损失。
但是,还有一个问题也是需要考虑的:我该以什么粒度去生成代码?
1. 是为每个反射调用生成代码?
2. 还是为每个类型批量生成一段代码?
3. 还是为一堆类型大批量的生成一批代码?

由于动态编译的结果并不能直接调用,我们只能借助委托或者接口的方式去调用,
所以如果每次代码生成的粒度较小,将会产生大量的程序集,也会消耗较多的编译器启动时间,
因此,这并不是高效的做法。高效的做法应该是一次尽可能生成较多的代码。

除此之外,还有一个问题也要考虑:当需要循环调用编译结果时,该怎么办?
对于这类场景,我建议在生成代码时,把循环过程直接生成出来,最终只用一次调用编译结果完成整个调用过程。
例如我们可以为数据访问层生成这样类似的代码,把循环、创建实体对象,以及给属性赋值的所有操作全部包含进来:

public static List<Product> LoadProduct(DbDataReader reader)
{
List<Product> list = new List<Product>(); while( reader.Read() ) {
Product p = new Product();
p.ProductID = (int)reader["ProductID"];
p.ProductName = reader["ProductName"].ToString();
p.CategoryID = (int)reader["CategoryID"];
p.Unit = reader["Unit"].ToString();
p.UnitPrice = (decimal)reader["UnitPrice"];
p.Remark = reader["Remark"].ToString();
p.Quantity = (int)reader["Quantity"];
list.Add(p);
}
return list;
}

如果我们生成了这样的代码,最后只需要一次调用,就可以代替以前上百次的委托调用以及缓存查找,锁的冲突也会减少到最低。

用CodeDOM优化反射的缺点

缺点有三个:
1. 方法不通用,需要针对不同的类型,不同的数据源生成不同的直接调用代码,因此难以通用化。
2. 复杂性较高,由于是生成直接调用的代码,且数据类型及格式未知,因此需要周密的考虑各种情况,复杂性也随之增高。
3. 难以封装,由于编译的结果是一个程序集,它并不能直接调用,还需要借助其它的方式来调用,所以难以实现较为通用的封装。

能不能不使用委托?

既然我们可以在运行时动态生成代码并编译它们,达到代替反射的目标,因此也就不需要委托调用的优化方法了。
那么,委托还有意义吗? 或者说:优化反射时能不能不使用委托?

在上篇博客中,我演示过动态编译的方法。
由于动态编译的结果是一个程序集,它本身是不能直接调用,我们需要采用其它的方法去调用它。
那篇博客给大家介绍了二种方法,其中一种方法就是用委托去调用程序集中的方法。
由于那些在运行时生成的代码是由我们的代码生成的,方法的签名我们可以控制,
所以,这时调用 Delegate.CreateDelegate 方法您不会遇到任何麻烦,
因此,通过强类型的委托来调用CodeDOM的编译结果,这种配合会非常方便。
正是由于这个原因,当您选择生成static类型的方法时,委托还是必须的,此时委托和CodeDOM将是一种共存关系。

如果您在生成代码时采用了接口的设计方案,那么委托就没有必要使用了。

根据反射密集程度选择优化方法

优化反射,到底是选择CodeDOM,还是选择Delegate ?
我认为要按不同的反射密集程度分开讨论。

1. 反射密集程度低:例如:一次HTTP请求过程,我们的代码只需要一二次反射操作,
或者对于桌面程序来说,在响应用户点击事件时,使用了几次反射调用。
在这类场景中,反射的密集程度就可认为是很低的。那么这种情况下该如何优化呢?
我的答案是:优不优化都无所谓,因为反射并不是慢得不能接受。
反射的速度到底有多慢? 我们还是来看一下以前做过的测试吧:

从这张图片(来源于本系列的第一篇)可以看出,用反射的方式执行属性赋值操作,就算运行1000000次,也只花了1.2秒! 要知道我的测试机器是3年前买的笔记本电脑,如果换成目前专业的服务器,消耗的时间会更少, 因此,这类反射的优化价值不大。 当然了,如果您愿意优化它们那也不是件坏事。

2. 反射密集程度高:例如,数据访问层的应用中, 当一次加载一个实体列表时,反射次数是分页数量乘以字段数量,再加上创建实体对象数量。 这个数量很容易达到百次级别,而且一次HTTP请求过程中,可能需要加载多种数据,那么反射次数就很可观了。 我们经常感觉各种序列化和反序列化程序的执行效率不高,这与反射有着很直接的关系。 不过,我们通常不需要编写序列化反序列化程序,也只能被迫接受它们的性能了。 因此,对于反射密集程度很高的代码,如果优化手段不理想,肯定会影响性能。

3. 当处于前二者之间的密集程度。由于这类场景实在是无法定性衡量, 而且不同人对性能敏感程度也不一样,或者由于不同的应用对性能的要求也不同。
因此,这类场景的范围只能靠自己去评估了,优化方式也只能是自行选择了:
1. 关注性能的话,就选择CodeDOM,
2. 否则就选择Delegate吧,毕竟这种方法使用简单。

CodeDOM优化的误区

1. CodeDOM真能让程序的性能提升千倍吗?
根据前面的截图,我们知道直接调用比反射调用的性能要提升千倍, 因此是不是可以认为采用动态编译的方法,程序的性能就能提升千倍?
答案是否定的。举例来说,拿创建实体对象的场景来说,虽然反射调用所花时间和直接调用时间差了千倍, 即使我们用动态编译代替了反射,但是给属性赋值前,我们需要为那些属性获取数据。 然而,获取数据的操作极有可能比反射更慢,因此,对于整个过程来说,我们能优化的只是其中的一小部分, 所以,当我们测试整个过程时,性能不会提升到千倍。 性能提升多少倍,取决于反射在整个过程中所花时间的比例。

2. CodeDOM方案一定比Delegate方案快。
答案也是否定的,前面已经解释过了,如果您为每个反射调用去生成一个方法(委托的思路),那么最后还是需要一个委托或者一个接口来调用, 而且此时还要加上编译器的启动时间,最终的性能将比委托更慢。

反射优化的总结

反射优化的根本方法只有一条路:避开反射。
然而,避开的方法可分为二种:
1. 用委托去调用。(绕弯子)
2. 生成直接调用代码,替代反射调用。(直截了当)

这二种方法都有优缺点,我认为选择哪种方法应该根据反射场景来决定:
1. 调用目标明确(名称和类型都是已知):强类型委托方法是较好的选择。
2. 调用目标不明确,且调用程度密集:动态编译方法是最好的选择。
3. 其它情况:可以用弱类型委托,或者不优化。

C# 之 反射性能优化3的更多相关文章

  1. C# 之 反射性能优化2

    问题回顾 在上篇博客中,我介绍了优化反射的第一个步骤:用委托调用代替直接反射调用. 然而,那只是反射优化过程的开始,因为新的问题出现了:如何保存大量的委托? 如果我们将委托保存在字典集合中,会发现这种 ...

  2. C# 之 反射性能优化1

    反射是一种很重要的技术,然而它与直接调用相比性能要慢很多,因此如何优化反射性能也就成为一个不得不面对的问题. 目前最常见的优化反射性能的方法就是采用委托:用委托的方式调用需要反射调用的方法(或者属性. ...

  3. 反射(4)反射性能问题:直接调用vs反射调用

    很多人都说使用反射会有性能问题,那到底会比直接调用慢多少呢,下面就来测试一下. 直接调用vs反射调用 下面就来写个demo来验证下直接调用和反射调用的性能差异,代码如下: namespace Cons ...

  4. 如何利用缓存机制实现JAVA类反射性能提升30倍

    一次性能提高30倍的JAVA类反射性能优化实践 文章来源:宜信技术学院 & 宜信支付结算团队技术分享第4期-支付结算部支付研发团队高级工程师陶红<JAVA类反射技术&优化> ...

  5. java反射之-性能优化

    在最近的计划中,打算看看在不使用google protobuf的情况下,在原有的采用jackson作为json序列化工具的基础上,是否可以实现进一步的性能优化.主要是针对list的情况. 测试的时候选 ...

  6. 利用表达式树Expression优化反射性能

    最近做了一个.Net Core环境下,基于NPOI的Excel导入导出以及Word操作的服务封装,涉及到大量反射操作,在性能优化过程中使用到了表达式树,记录一下. Excel导入是相对比较麻烦的一块, ...

  7. 深入分析Java反射(八)-优化反射调用性能

    Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行Deb ...

  8. Unity性能优化(4)-官方教程Optimizing graphics rendering in Unity games翻译

    本文是Unity官方教程,性能优化系列的第四篇<Optimizing graphics rendering in Unity games>的翻译. 相关文章: Unity性能优化(1)-官 ...

  9. Android客户端性能优化(魅族资深工程师毫无保留奉献)

    本文由魅族科技有限公司资深Android开发工程师degao(嵌入式企鹅圈原创团队成员)撰写,是degao在嵌入式企鹅圈发表的第一篇原创文章,毫无保留地总结分享其在领导魅族多个项目开发中的Androi ...

随机推荐

  1. Java常见异常及解释

  2. 第八章 让Bootstrap轮播插件carousel支持左右滑动手势的三种方法

    因为最近开发的项目涉及到移动设备上的 HTML5 开发,其中需要实现轮播效果.然后最快捷的方式,你知道的(Bootstrap),然后原生的 Bootstrap 的 carousel.js 插件并没有支 ...

  3. pl sql 中文乱码

    一:查看oracle数据库的字符集编码: select * fromnls_database_parameters where parameter in ('NLS_LANGUAGE', 'NLS_T ...

  4. ansible笔记(9):常用模块之包管理模块

    ansible笔记():常用模块之包管理模块 yum_repository模块 yum_repository模块可以帮助我们管理远程主机上的yum仓库. 此处我们介绍一些yum_repository模 ...

  5. maven项目导入war包

    导入war包,使用war包中的类和静态资源(css,js,包含jsp等) A项目是被导入的war工程,B项目是需要A项目的一个工程. 在<build>中 <resources> ...

  6. 常用的ORM框架

    现在,很多项目使用ORM的框架构架实现数据持久层,下面列举一些常用的ORM框架有,后续分节介绍. Java:Hibernate和Mybatis(前身iBatis) .Net:EF6与EFCore.Da ...

  7. 一个简单的Loading控件

    实现效果如下: 使用方法: 在layout文件中添加以下代码: <com.example.jack.ui.widget.RingLoading android:layout_width=&quo ...

  8. Struts2,springMVC获取request和response

    springMVC获取request和response1:在BaseController中加入: protected HttpServletRequest request;   protected H ...

  9. mysql针对单个库进行备份

    #!/bin/bashBakDir=/backup/mysql_backupLogFile=/backup/mysql_backup.logDate=`date +%Y%m%d`Begin=`date ...

  10. Connection reset by [server_ip] port 22 (hexo d 部署博客出错)

    问题 在使用 hexo d 部署博客和使用 Git/Github 进行 git push -u origin master 时遇到了以下问题: git -c diff.mnemonicprefix=f ...