反射性能的比较和改进

关于反射“时间性能”比较,最近又在园子里面看到一篇关于反射性能的文章,所以又去翻了下资料对反射做了进一步的了解。这篇是文章是对“时间性能”的补充及加入“空间性能”的延伸。

一、时间性能  (InvokeCompare.cs)

n  回顾

在前一篇文章中已经对三种方法调用方式进行过比较。分别为:

1.         直接调用方法

2.         反射并缓存MethodInfo对象进行调用(缓存 + 反射)

3.         DynamicMethod动态生成IL方法进行调用

分别进行1000000次循环调用结果如下:

 image

    这个结果已经告诉我们反射调用成员的方式是比较慢的,反射为何效率低下,原因是:

a)         搜索:使用 System.Reflection 命名空间中的类型扫描程序集的元数据时,反射要不断的执行字符串的搜索。通常,搜索时不区分大小写的比较,这会更进一步影响性能。

b)         调用:使用反射调用一个成员时。比如调用方法,首先必须将实参打包(pack)成一个数组;在内部,反射必须将这些实参解包(unpack)到线程栈上。此外,在调用方法前,CLR必须检查实参具有正确的数据类型。最后,CLR必须确保调用者有正确的安全权限来访问被调用的成员。

n  补充

今天这边再补充几种成员调用方式进行比较:

1.         反射获取MethodInfo后,挂接到委托上进行调用(缓存 + 反射 + 委托)

——注意构造器和字段不能挂接委托

2.         使用 Activator.CreateInstance() 动态创建对象,在对象上进行方法调用

a)         使用Type.InvokeMember()方式,一种蛋疼的方式

b)         使用Dynamic方式承接对象进行方法调用。(但在分布式架构中我们无法得知方法名,所以我们可能会创建一个“通用方法”,“通用方法”见“空间性能”说明)

clip_image004

结果肯定让你惊喜的发现:当反射成员挂接到委托上,效率能提升到和直接调用“不相上下”。(这个“不相上下”根据类中成员数量会有不同,示例代码中使用了T4模板机制,有兴趣的园友可以自行改变生成规则)

最让你蛋疼的肯定是 Activator.CreateInstance() + InvokeMember() 的方式了(可查看《反射:(9)程序集的加载和反射》),明白了就不要去用了。

n  结论:时间性能上比较

(直接调用)>=(缓存 + 反射 +委托)>(DynamicMethod + 委托)>(Activator方式)>(缓存 + 委托)

(在示例代码中我还进行了对单个对象上得多个方法进行调用比较,性能结果顺序是一致的。原本想测试下 Activator.CreateInstance() 对象后,再操作这个对象上多个方法效率会不会高于别的方式)

 

二、空间性能  (InvokeCompare.cs)

正如大家所知:多数项目都是有“正反”两面的,只有极少数如《人民日报》、《环球日报》、《新闻联播》等项目中才只有“正面”,因为这当中不包括微软,所以下面我再从“空间”上分析下上面几种调用方式。

         本示例通过 GC.GetTotalMemory(true) 获取所耗内存字节数,我们还需要了解下“字节换算”:8bit(位)=1Byte(字节) ; 1024Byte(字节)=1KB ; 1024KB=1MB ; 1024MB=1GB ; 1024GB=1TB

1.         (缓存 + 反射)方式,缓存MethodInfo

2.         (缓存 + 反射 + 委托)方式:

a)         缓存 MethodInfo + 委托集合

b)         MethodInfo + 单个委托

3.         (DynamicMethod + 委托)方式,缓存委托

4.         (Activator.CreateInstance)方式,缓存实例对象

clip_image006

n  结论:空间性能上比较

1.         (缓存 + 反射)方式:尽管所耗内存是最小的,但因为其“时间性能”所以我们是要避免使用此方式的。我这边列出来主要是想和第二种方式缓存“单个委托”所耗内存进行个参照。

2.         (缓存 + 反射 + 委托)方式

a)         委托要及时释放:通过是否缓存委托的内存比较只是想说明委托会占用比较大的内存,因为delegate关键字申明的委托会被编译成继承自Delegate类的类,从上面红色框框标出的缓存“委托集合”和“单个委托”内存差8倍之大,可以说明Delegate类的构造函数有初始化比较多的东西。

b)         缓存“单个委托”方式最佳:从整个结果来看,只缓存“单个委托”是最佳后期绑定方式。(比如事件签名就相当统一)

3.         DynamicMethod + 委托,适用于插件机制:就数据来看此方式耗费内存最大,当然用这种方式去比较对它是不公平的,因为正确的适用场合是 “动态构造程序集 + 动态构造方法”从而实现插件机制,要知道程序集加载到应用程序后是要等到程序关闭才会释放,而这种动态方式的插件机制则可以做到用完即可释放的效果。(实际上我们也可以创建新域Appdomain,让程序集绑定到新域上从而实现相同卸载效果——对于他们之间的优劣我了解甚少,有相关资料的园友还请多多推荐资料)

或则用在委托不好做的地方:比如Fast Dynamic Property/Field Accessors》

4.         Activator.CreateInstance() 方式:很多朋友看到上图后马上得出结论:只需要“缓存 + 反射 + 单个委托”方式。但是我想表达的是“尺有所短,寸有所长”。

示例:比如一大型ERP软件的分布式架构中

a)         服务器:为了节约服务器资源,所以各个模块是根据请求动态加载,并缓存各个功能类,因为多用户连接需求所以各个功能类以“对象池”的方式缓存在服务器。(试想一下缓存MemberInfo,那数量是多么庞大)

b)         客服端:客户端调用服务器的方法时通常要传“类名”和“方法名”两个参数,服务器根据请求 Activator.CreateInstance() 对象,在这个“类”中我们可以声明一个“通用方法”(通用方法的作用是避免使用Type.InvokeMember()去调用成员)根据“方法名”参数去switch调用方法。

从这个例子中可以看到方式(4)是在分布式架构中常用的一种方式,而方式(2)更始用于某一功能块中对MemberInfo进行缓存调用。

 

三、使用绑定句柄来减少进程的内存耗用  (HandleMemory.cs)

详见:CLR via C#(第三版)

         许多应用程序绑定了一组类型(Type 对象)或者类型成员(从MemberInfo 派生的对象),并将这些对象保存在某种形式的一个集合中,以后应用程序搜索这个集合,查找特定的对象,然后调用这个对象。这是一个很好的机制,只是有一个问题:Type和MemberInfo派生对象需要大量内存。因此,如果一个应用程序容纳了太多这样的对象,但只是偶尔调用一下它们,应用程序的内存耗用就急剧增长,对应用程序的性能产生负面影响。

         在内部,CLR用一种更精简的方式表示这种信息。CLR之所以为应用程序创建这些对象,只是为了简化开发人员的工作。CLR在运行时本身并不需要这些大对象。如果需要保存缓存大量Type和MemberInfo派生对象,开发人员可以使用运行时句柄(runtime handles)来代替对象,从而减小工作集(占用的内存)。FCL定义了三个运行时句柄类型:RuntimeTypeHandle、RuntimeFieldHandle、RuntimeMethodHandle。三个类型都是值类型,它们只包含一个句柄字段IntPrt,它引用了AppDomain的Loader堆中的一个类型、字段或方法。

1.         转换:RuntimeTypeHandle 与Type

Type.GetTypeFromHandle() 方法

 Type 实例的 TypeHandle 属性

2.         转换:RuntimeFieldHandle与FieldInfo

FieldInfo.GetFieldFromHandle() 方法

 FieldInfo 实例的 FieldHandle 属性

3.         转换:RuntimeMethodHandle与MethodInfo

 MethodInfo.GetMethodFromHandle() 方法

 MethodInfo 实例的 MethodHandle 属性

n  结论:缓存轻量级的运行时句柄 (RuntimeTypeHandleRuntimeFieldHandleRuntimeMethodHandle)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值