探究.net对象的创建,质疑《再谈Activator.CreateInstance(Type type)方法创建对象和Expression Tree创建对象性能的比较》

    今日, Will Meng发布了一篇名为《 再谈Activator.CreateInstance(Type type)方法创建对象和Expression Tree创建对象性能的比较》的博文,文中通过一系列的对比测试,得到了一个结论:” 结果方法Activator.CreateInstance()比表达式树要快了。”
 然而,在我个人的使用中,我对这个结果产生了质疑,本着追根究底的思想,抽了个时间我做了个更详细的测试,测试主要针对有如下几种构造对象的方法:
1. 直接初始化
2. 通过ExpressionTree构造委托来初始化
3. 通过Emit构造委托初始化
4. 调用Activator.CreateInstanse(Type type)初始化
5. 调用Activator.CreateInstanse()初始化
6. 使用传统反射进行初始化
在测试过程中,使用老赵的CodeTimer计时,测试代码如下:
代码
            Type type  =   typeof (TestClass);
            Func
< TestClass >  fun  =  GetCreateFunc();
            fun();
            Func
< TestClass >  fun2  =  GetEmitCreateFunc();
            fun2();
            Activator.CreateInstance(type);
            Activator.CreateInstance
< TestClass > ();
            var method 
=  type.GetConstructors()[ 0 ];
            method.Invoke(
null );
            GetCache.InstanceCacheEx.InstanceCache(type);
            
int  num  =   1000000 ;
            CodeTimer.Initialize();
            CodeTimer.Time(
" NewInstanse " , num, ()  =>  {
                
new  TestClass();
            });
            CodeTimer.Time(
" ExpressionCreate " , num, ()  =>  {
                fun();
            });
            CodeTimer.Time(
" EmitCreate " , num, ()  =>  {
                fun2();
            });
            CodeTimer.Time(
" ActivatorCreate " , num, ()  =>  {
                Activator.CreateInstance(type);
            });
            CodeTimer.Time(
" ReflectionCreate " , num, ()  =>  {
                method.Invoke(
null );
            });
            CodeTimer.Time(
" GenericCreate " , num, ()  =>  {
                Activator.CreateInstance
< TestClass > ();
            });

    测试过程为构造100w次简单对象.测试结果如下图所示:
探究.net对象的创建,质疑《再谈Activator.CreateInstance(Type type)方法创建对象和Expression Tree创建对象性能的比较》_第1张图片
    查看测试结果,很显然,和Will Meng的结果大不一样.在上面的测试结果中,直接初始化速度最快,ExpressionTree和Edmit初始化速度基本相同,和直接初始化相比只慢了少许 ,其次是Activator.CreateInstanse方法,让人大吃一惊的是Activator.CreateInstanse方法居然是最慢的.于是,现在产生了两个疑问:
    1. 为什么这份测试结果和Will Meng相差这么多
    2. 为什么Activator.CreateInstanse比传统反射还慢
    首先看第一个疑问,对了得到最终结果,我们首先将Will Meng的测试环境重现,将这部分代码也放入我的测试项目,不过在重现过程中出现一个小问题
探究.net对象的创建,质疑《再谈Activator.CreateInstance(Type type)方法创建对象和Expression Tree创建对象性能的比较》_第2张图片
    我对这行无法通过编译的进行了简单的修改.然后进行了测试,我将Will Meng构造的代码放入CodeTimer进行了测试,同时copy了他的测试代码也同时进行了测试,测试结果如下图(由于屏幕高度有限,这个图片分为两部分截取):
探究.net对象的创建,质疑《再谈Activator.CreateInstance(Type type)方法创建对象和Expression Tree创建对象性能的比较》_第3张图片
探究.net对象的创建,质疑《再谈Activator.CreateInstance(Type type)方法创建对象和Expression Tree创建对象性能的比较》_第4张图片
    CodeTimer的结果显示,Will Men构造的关于ExpressionTree的代码仍然具有相当高的效率,但是仍比直接委托执行慢了很多,个人认为原因就在于多余的代码,比如字典,方法调用等消耗了不少时间.然而在我的系统上,完全和Will Men一样的测试代码也表现出了不同的测试结果,在我的测试环境中,Expression.CreateInstanseEx比Activator.CreateInstanse消耗的时间少2/3还多.因此,我对Will Men的测试感到实在是很疑惑,同时也希望有朋友能帮忙解答为何同样的代码,测试结果相差如此之多.
    至此,第一个问题算是基本解决,在我个人的测试环境下,ExpressionTree构造对象很显然效率远高于Activator.CreateInstanse.
    下面解决第二个问题,为什么Activator.CreateInstanse要比Activator.CreateInstanse慢了这么多,甚至于比直接反射还要慢,要解决这个问题,我们得请出Reflector工具来查看这两个方法的具体实现,也许我们会想当然的认为Activator.CreateInstanse是调用Activator.CreateInstanse方法然后转换对象为T,但是Reflector查看的结果却不是如此,微软对这两个方法进行了完全不同的实现,下面看看Activator.CreateInstanse的实现:

    可以看到,该方法最后调用RuntimeTypeHandle.CreateInstance方法来构造对象然后返回,接下来我们看看Activator.CreateInstanse的实现,Activator.CreateInstanse(Type type)间接调用了Activator.CreateInstanse(Type type, bool nonpublic),因此,我们直接查看该方法的实现:
探究.net对象的创建,质疑《再谈Activator.CreateInstance(Type type)方法创建对象和Expression Tree创建对象性能的比较》_第5张图片
    很显然,这两个方法采用了不一样的方式,该方法最后调用underlyingSystemType.CreateInstanceImpl构造对象,为了得到最后答案,我们继续查看underlyingSystemType.CreateInstanceImpl方法的实现:

    仍然是一个中转调用,继续查看:
探究.net对象的创建,质疑《再谈Activator.CreateInstance(Type type)方法创建对象和Expression Tree创建对象性能的比较》_第6张图片
    终于看到了关键性实现,在这儿,该方法采用了ActivatorCache的方式进行了缓存,在未缓存的时候,会调用CreateInstanceSlow,我们继续查看CreateInstanceSlow的实现:
探究.net对象的创建,质疑《再谈Activator.CreateInstance(Type type)方法创建对象和Expression Tree创建对象性能的比较》_第7张图片
    在这儿,我们看到了熟悉的RuntimeTypeHandle.CreateInstance来构造未缓存对象,很明显,微软也知道RuntimeTypeHandle.CreateInstance的效率并不高,因此Activator.CreateInstanse方法采用缓存的方式来提高效率,但是很是让人奇怪的是Activator.CreateInstanse方法却绕过了缓存而直接调用低效率方法,这种做法实在让人不解.
    现在,我们揭开了开头提出的两个疑问,在本文中,可以得到一个结论:在对象的构造中,我们优先采用直接构造的方式,在某些需要动态创建的环境下,尽可能优先采用ExpressionTree或者Emit的方式构造,如果这部分无法满足要求,可以考虑使用效率稍低的Activator.CreateInstanse方法,而效率更低的反射调用一般不在我们的考虑之列了,最后最重要一点是,一般来说,特别是对效率有要求的地方,尽可能不去使用Activator.CreateInstanse方法.

 

补充部分:

看到Activator.CreateInstanse的糟糕设计,有朋友直接质疑应该是临时工干的,因此个人又查看了.net4.0 beta2中该方法的实现,如下图:
探究.net对象的创建,质疑《再谈Activator.CreateInstance(Type type)方法创建对象和Expression Tree创建对象性能的比较》_第8张图片

继续查看CreateInstanseDefaultCtor
探究.net对象的创建,质疑《再谈Activator.CreateInstance(Type type)方法创建对象和Expression Tree创建对象性能的比较》_第9张图片

看样子,新的framework中这个问题得到了修正.

你可能感兴趣的:(探究.net对象的创建,质疑《再谈Activator.CreateInstance(Type type)方法创建对象和Expression Tree创建对象性能的比较》)