《(译)一个通用快速的反射方法调用》续篇

 

源码下载:示例代码_for_一个通用快速的反射方法调用

 

Hello,前段时间在园子发了一篇《(译)一个通用快速的反射方法调用》的译文,关于反射时间性能比较。这篇文章是对时间性能的补充及加入空间性能的延伸。

一、时间性能  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 派生的对象),并将这些对象保存在某种形式的一个集合中,以后应用程序搜索这个集合,查找特定的对象,然后调用这个对象。这是一个很好的机制,只是有一个问题:TypeMemberInfo派生对象需要大量内存。因此,如果一个应用程序容纳了太多这样的对象,但只是偶尔调用一下它们,应用程序的内存耗用就急剧增长,对应用程序的性能产生负面影响。

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

1.         转换:RuntimeTypeHandle Type

Type.GetTypeFromHandle() 方法

 Type 实例的 TypeHandle 属性

2.         转换:RuntimeFieldHandleFieldInfo

FieldInfo.GetFieldFromHandle() 方法

 FieldInfo 实例的 FieldHandle 属性

3.         转换:RuntimeMethodHandleMethodInfo

 MethodInfo.GetMethodFromHandle() 方法

 MethodInfo 实例的 MethodHandle 属性

示例运行截图

image

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

 

推荐阅读:

             优化反射性能的总结(上)

             优化反射性能的总结(中)

             优化反射性能的总结(下)

             替代反射调用的几种方式及性能测试

             反射是否真的会让你的程序性能降低?

相关参考:

         书籍《CLR via C#(第三版)》

         反射:(9)程序集的加载和反射

 

posted on 2012-07-11 17:20  滴答的雨  阅读(4868)  评论(8编辑  收藏  举报