今日, Will Meng发布了一篇名为《再谈Activator.CreateInstance(Type type)方法创建对象和Expression Tree创建对象性能的比较》的博文,文中通过一系列的对比测试,得到了一个结论:” 结果方法Activator.CreateInstance()比表达式树要快了。”
 然而,在我个人的使用中,我对这个结果产生了质疑,本着追根究底的思想,抽了个时间我做了个更详细的测试,测试主要针对有如下几种构造对象的方法:
1. 直接初始化
2. 通过ExpressionTree构造委托来初始化
3. 通过Emit构造委托初始化
4. 调用Activator.CreateInstanse(Type type)初始化
5. 调用Activator.CreateInstanse<T>()初始化
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次简单对象.测试结果如下图所示:

    查看测试结果,很显然,和Will Meng的结果大不一样.在上面的测试结果中,直接初始化速度最快,ExpressionTree和Edmit初始化速度基本相同,和直接初始化相比只慢了少许 ,其次是Activator.CreateInstanse方法,让人大吃一惊的是Activator.CreateInstanse<T>方法居然是最慢的.于是,现在产生了两个疑问:
    1. 为什么这份测试结果和Will Meng相差这么多
    2. 为什么Activator.CreateInstanse<T>比传统反射还慢
    首先看第一个疑问,对了得到最终结果,我们首先将Will Meng的测试环境重现,将这部分代码也放入我的测试项目,不过在重现过程中出现一个小问题

    我对这行无法通过编译的进行了简单的修改.然后进行了测试,我将Will Meng构造的代码放入CodeTimer进行了测试,同时copy了他的测试代码也同时进行了测试,测试结果如下图(由于屏幕高度有限,这个图片分为两部分截取):


    CodeTimer的结果显示,Will Men构造的关于ExpressionTree的代码仍然具有相当高的效率,但是仍比直接委托执行慢了很多,个人认为原因就在于多余的代码,比如字典,方法调用等消耗了不少时间.然而在我的系统上,完全和Will Men一样的测试代码也表现出了不同的测试结果,在我的测试环境中,Expression.CreateInstanseEx比Activator.CreateInstanse消耗的时间少2/3还多.因此,我对Will Men的测试感到实在是很疑惑,同时也希望有朋友能帮忙解答为何同样的代码,测试结果相差如此之多.
    至此,第一个问题算是基本解决,在我个人的测试环境下,ExpressionTree构造对象很显然效率远高于Activator.CreateInstanse.
    下面解决第二个问题,为什么Activator.CreateInstanse<T>要比Activator.CreateInstanse慢了这么多,甚至于比直接反射还要慢,要解决这个问题,我们得请出Reflector工具来查看这两个方法的具体实现,也许我们会想当然的认为Activator.CreateInstanse<T>是调用Activator.CreateInstanse方法然后转换对象为T,但是Reflector查看的结果却不是如此,微软对这两个方法进行了完全不同的实现,下面看看Activator.CreateInstanse<T>的实现:

    可以看到,该方法最后调用RuntimeTypeHandle.CreateInstance方法来构造对象然后返回,接下来我们看看Activator.CreateInstanse的实现,Activator.CreateInstanse(Type type)间接调用了Activator.CreateInstanse(Type type, bool nonpublic),因此,我们直接查看该方法的实现:

    很显然,这两个方法采用了不一样的方式,该方法最后调用underlyingSystemType.CreateInstanceImpl构造对象,为了得到最后答案,我们继续查看underlyingSystemType.CreateInstanceImpl方法的实现:

    仍然是一个中转调用,继续查看:

    终于看到了关键性实现,在这儿,该方法采用了ActivatorCache的方式进行了缓存,在未缓存的时候,会调用CreateInstanceSlow,我们继续查看CreateInstanceSlow的实现:

    在这儿,我们看到了熟悉的RuntimeTypeHandle.CreateInstance来构造未缓存对象,很明显,微软也知道RuntimeTypeHandle.CreateInstance的效率并不高,因此Activator.CreateInstanse方法采用缓存的方式来提高效率,但是很是让人奇怪的是Activator.CreateInstanse<T>方法却绕过了缓存而直接调用低效率方法,这种做法实在让人不解.
    现在,我们揭开了开头提出的两个疑问,在本文中,可以得到一个结论:在对象的构造中,我们优先采用直接构造的方式,在某些需要动态创建的环境下,尽可能优先采用ExpressionTree或者Emit的方式构造,如果这部分无法满足要求,可以考虑使用效率稍低的Activator.CreateInstanse方法,而效率更低的反射调用一般不在我们的考虑之列了,最后最重要一点是,一般来说,特别是对效率有要求的地方,尽可能不去使用Activator.CreateInstanse<T>方法.

 

补充部分:

看到Activator.CreateInstanse<T>的糟糕设计,有朋友直接质疑应该是临时工干的,因此个人又查看了.net4.0 beta2中该方法的实现,如下图:

继续查看CreateInstanseDefaultCtor

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

posted on 2009-12-08 12:20  Leven  阅读(9786)  评论(46编辑  收藏  举报
CopyRight 2008, Leven's Blog xhtml | css
Leven的个人Blog