C# 之 反射性能优化3
阅读目录
在前二篇博客中,我分别介绍了二种优化反射的方法:
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的更多相关文章
- C# 之 反射性能优化2
问题回顾 在上篇博客中,我介绍了优化反射的第一个步骤:用委托调用代替直接反射调用. 然而,那只是反射优化过程的开始,因为新的问题出现了:如何保存大量的委托? 如果我们将委托保存在字典集合中,会发现这种 ...
- C# 之 反射性能优化1
反射是一种很重要的技术,然而它与直接调用相比性能要慢很多,因此如何优化反射性能也就成为一个不得不面对的问题. 目前最常见的优化反射性能的方法就是采用委托:用委托的方式调用需要反射调用的方法(或者属性. ...
- 反射(4)反射性能问题:直接调用vs反射调用
很多人都说使用反射会有性能问题,那到底会比直接调用慢多少呢,下面就来测试一下. 直接调用vs反射调用 下面就来写个demo来验证下直接调用和反射调用的性能差异,代码如下: namespace Cons ...
- 如何利用缓存机制实现JAVA类反射性能提升30倍
一次性能提高30倍的JAVA类反射性能优化实践 文章来源:宜信技术学院 & 宜信支付结算团队技术分享第4期-支付结算部支付研发团队高级工程师陶红<JAVA类反射技术&优化> ...
- java反射之-性能优化
在最近的计划中,打算看看在不使用google protobuf的情况下,在原有的采用jackson作为json序列化工具的基础上,是否可以实现进一步的性能优化.主要是针对list的情况. 测试的时候选 ...
- 利用表达式树Expression优化反射性能
最近做了一个.Net Core环境下,基于NPOI的Excel导入导出以及Word操作的服务封装,涉及到大量反射操作,在性能优化过程中使用到了表达式树,记录一下. Excel导入是相对比较麻烦的一块, ...
- 深入分析Java反射(八)-优化反射调用性能
Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行Deb ...
- Unity性能优化(4)-官方教程Optimizing graphics rendering in Unity games翻译
本文是Unity官方教程,性能优化系列的第四篇<Optimizing graphics rendering in Unity games>的翻译. 相关文章: Unity性能优化(1)-官 ...
- Android客户端性能优化(魅族资深工程师毫无保留奉献)
本文由魅族科技有限公司资深Android开发工程师degao(嵌入式企鹅圈原创团队成员)撰写,是degao在嵌入式企鹅圈发表的第一篇原创文章,毫无保留地总结分享其在领导魅族多个项目开发中的Androi ...
随机推荐
- FHQ Treap摘要
原理 以随机数维护平衡,使树高期望为logn级别 不依靠旋转,只有两个核心操作merge(合并)和split(拆分) 因此可持久化 先介绍变量 ; int n; struct Node { int v ...
- .net 项目与网站区别
背景 .net 的又一个杰作,我作为资深开发人员,好久没搞明白两者关系,后来慢慢总算琢磨明白了.在2003和2005的时候,都是用的网站方式,后来见到某同事用的项目方式,当时还很不理解,真是个傻瓜程序 ...
- python操作Excel的库openpyxl
http://openpyxl.readthedocs.io/en/default/tutorial.html 这里先上该库的文档镇文. 1,遇到合并后的单元格信息读取的问题,通过使用cell中off ...
- FastCGI sent in stderr: "PHP Warning: simplexml_load_string(): Entity: line 1: parser error : Start tag expected, '<' not found in
2018-4-16 17:59:03 星期一 1. 发送带有xml参数的请求时如果是用php curl, 需要将数组形式的post data 用 http_build_query() 转换 2. 接收 ...
- bash的快捷键、特殊参数、历史命令、相关文件
bash快捷键 Emacs风格 ctrl+p: 方向键 上 ↑ ctrl+n: 方向键下 ↓ ctrl+b: 方向键 ← alt+f: 光标右移一个单词 ctrl+f :方向键 → alt+b: 光标 ...
- 如何查看当前应用包名和activity
这里提供一个简单的方法来获取package和activity: 在Android模拟器上打开微信APP,然后打开CMD,输入以下命令: adb shell 接下来在#后面继续输入以下命令: logca ...
- 小甜点,RecyclerView 之 ItemDecoration 讲解及高级特性实践
本篇文章摘自微信公众号 guolin_blog (郭霖)独家发布 毫无疑问,RecyclerView 是现在 Android 世界中最重要的系统组件之一,它的出现就是为了高效代替 ListView 和 ...
- login_code
#! -*- coding:utf-8 -*-"""http://www.cnblogs.com/weke/articles/6271206.html前置和后置1.set ...
- Confluence 6 数据库表-空间(Spaces)
这个表格与空间的管理有关. spaces 有关空间使用的信息:key,空间的名称和数字 ID. https://www.cwiki.us/display/CONF6ZH/Confluence+Data ...
- Confluence 6 用户提交的备份和恢复脚本
下面的代码是用户提交的,在使用的时候需要小心,因为 Atlassian 不提供这些代码的技术支持.如果你在使用或者修改这些代码的时候有任何问题,请粘贴到 post them to Atlassian ...