现在的位置: 首页 > 综合 > 正文

绕开频繁反射

2011年03月30日 ⁄ 综合 ⁄ 共 1731字 ⁄ 字号 评论关闭
    在.net里面,反射是一个功能强大的工具,但是,这个强大功能背后却是性能的损失。尤其是频繁的反射,将降低总体性能。
    为了程序运行的更快更好,有必要在频繁反射的地方添加必要的缓存,用尽量少的反射,获得必要的灵活性,而又不降低太多性能。
    说到性能,先说一下在.net中的性能排列吧,最快的是非虚方法,然后依次是虚方法、接口方法,委托,反射。前面3个的性能几乎可以忽略。委托比一般的方法执行要来得慢(可以参考 Why is IComparer an Interface?),而反射,比委托更慢,总之,功能越强的越慢。。。
    因此,绕开频繁反射,将反射方法的调用转化为使用非虚方法、虚方法、接口方法、委托中的任何一种,性能都回提升。
    下面,来看看可行性。
    一、将反射变成委托,虽然提升的比较少,但是也是提升,来看一下如何转换:
    在.net 2.0中引入了一个新的类型,System.Reflection.Emit.DynamicMethod,这个类型可以创建一个动态方法,执行的内容根据GetILGenerator()方法返回的ILGenerator生成的代码,生成代码时把反射得到的MethodInfo放进去就可以了,再通过Delegate CreateDelegate(Type delegateType)返回一个委托,然后,只要去Invoke这个委托就可以了。
    优点:将第一次之后的反射调用,转化为调用委托。
    缺点:第一次执行时,除了反射还需要Emit一个动态方法。而且,每次反射一个不同的方法,就需要一个新的动态方法。
    二、将反射变成接口方法,这个方案可以把性能提高到接近静态代码的程度,来看一下如何实现:
    这次,需要全方位的使用Emit了。
    预备工作:预备一个公开的接口,调用的方法的签名必须和接口中方法一致(通常这条是满足的,如果不满足这条,就不能用这个方案了)。
    类型准备工作:首先需要创建一个动态程序集(可重用),再创建一个动态模块(可重用),创建动态类型,并符合接口,方法的实现部分,只需要调用反射获得的MethodInfo。再准备一个默认的构造函数。
    获得接口实例:创建出这个类型的一个实例,as成接口,用变量保存起来。
    以后,每次调用的时候,只需要调用接口的函数就可以了。
    优点:将第一次之后的反射调用,转化为调用接口方法。
    缺点:第一次执行时,除了反射还需要Emit一个完整的动态类型。而且,每次反射一个不同的方法,就需要一个新的动态类型。
    三、将反射变成虚方法,没有必要,就算要实现,和接口方法也差不太多,而且,性能没什么太大的差别,还没有接口灵活。
    四、将反射变成非虚方法,好像没法实现。。。

    这几个方式,都是以第一次的性能为牺牲,换取之后的性能提升。但是问题是,这个时间是否换的划算哪?
    这就要看反射会被调用多少次了,如果,一共才调个10次,显然,还不如直接反射来的快,根本没必要如此吃力不讨好。
    但是如果,是作为服务程序运行,并且会被高频率的反射,那么,这么做就有价值了。

    最后,留下一个更BT的做法,如果还没有对Emit或IL有足够的了解,建议跳过这一段。
    熟悉Emit的应该看到过Opcodes.Calli,按照MSDN上的说法,它会调用堆栈上的IntPtr所指向的函数,也就是说,只要获得某个方法的指针,就可以用这个Calli去掉用这个方法。问题变成如何获得这个方法的指针,虽然Opcodes.Ldftn和Opcodes.Ldvirtftn可以获得这个指针,但是显然,这样就没意义了(C#,VB.Net好像都没有提供这个方法的直接调用,要是为每一个反射建一个动态类型=没有改进。。。),从反射这里,仔细看一下System.Reflection.MethodBase,可以找到一个叫MethodHandle的属性,返回了一个RuntimeMethodHandle的结构体,而这个结构体提供了一个IntPtr GetFuntionPoint()的方法,它返回的指针就是Opcodes.Calli所需要的指针。再之后,我也不用说了,开始向C的函数指针靠拢。。。

抱歉!评论已关闭.