在前二篇博客中,我分别介绍了二种优化反射的方法:
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. ASP.NET WEBAPI 使用Swagger生成API文档

    一.安装 新建一个没有身份验证的mvc项目 - SwaggerMvc5Demo,然后添加一个名为Remote(自定义)且包含基础读写(不想手写)的ApiController   开源地址:https: ...

  2. vue中更换.ico图标报错路径找不到图片

    问题描述: vue项目中,想要更换.ico图片,更换完成后刷新页面报错,找不到路径. 解决: 更换完图片,重新启动下vue项目(npm run dev)就可以啦~ 哈哈哈 补充知识: 网页title旁 ...

  3. Android Day2

    紧接前一篇日记: 2.增加文本域 在<LinearLayout>中增加<EditText>元素,无格式设置的时候如下. <EditText android:id=&quo ...

  4. [转]PHP中file_put_contents追加和换行

    在PHP的一些应用中需要写日志或者记录一些信息,这样的话. 可以使用fopen(),fwrite()以及 fclose()这些进行操作. 也可以简单的使用file_get_contents()和fil ...

  5. swift 实践- 11 -- UISlider

    import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoa ...

  6. hibernate入门程序

    快速入门       1. 下载Hibernate框架的开发包       2. 编写数据库和表结构 Create database hibernate_day01; Use hibernate_da ...

  7. js调用ajax案例2,使用ok

    XMLHttpRequest 是 AJAX 的基础. XMLHttpRequest 对象所有现代浏览器均支持 XMLHttpRequest 对象(IE5 和 IE6 使用 ActiveXObject) ...

  8. IntelliJ IDEA使用教程 (总目录篇)

    注:本文来源于<    IntelliJ IDEA使用教程 (总目录篇)  > IntelliJ IDEA使用教程 (总目录篇) 硬件要求 IntelliJ IDEA 的硬件要求 安装包云 ...

  9. Confluence 6 编辑站点欢迎消息

    通过编辑欢迎信息能够为你站点的主页面添加一些个人信息. 站点的欢迎信息显示在站点主面板的右侧,这是你对站点添加声明,连接,有关你项目组美好回忆照片的完美位置. 你需要 Confluence 管理员权限 ...

  10. 第十九单元 nfs服务

    ===============服务端 介绍: NFS 是Network File System的缩写,即网络文件系统.一种使用于分散式文件系统的协定,由Sun公司开发,于1984年向外公布.功能是通过 ...