[c#] 反射真的很可怕吗?

    说起c#中的反射,相信很多人第一反应就是“性能低”,或者是"慢"。当一个人说你有问题,那可能是说你有问题的那个人自己有问题,但如果N多人说你有问题,那估计真的是你有问题,所以,我从来不争论,也不否认,也不怀疑反射比起直接调用性能要低。直接调用的代码是被编译好,按部就班执行就行了,但反射调用过程被推迟到运行期,是动态的,而很多情况下,动态就意味着性能的损失。有时反射还意味着动态加载(Assambly.LoadFrom),就不免要发生IO操作,那更是慢上加慢。

    可能因为反射慢这个事实,而事实经常被道听途说,甚至有些东西被以讹传讹后,后来竟然出现“万恶的反射”和“反射不可接受”等等态度。但是真的是不可接受吗?asp.net或者IIS中就有很多东西是反射加载的,例如iis中N多的http module。我想搞清楚反射为什么慢,背后到底发生了什么事情,于是google很不少资料,其中在stackoverflow上也提问过(在这里),其中有些人的意见是:”反射慢?看跟谁比较了;反射是慢,但对我们来说足以够快。“于是,我有了写这篇博客的念头。

    我的态度就是:首先,没错,反射是慢,但这是因为我们拿它跟直接调用做比较,反射并不是不可接受,只是因为直接调用太快了。php单从执行效率来说比c#慢多了,但谁也不能断定php比asp.net差,java很多地方也没有c#效率高,但who cares?大多时候我们不是做实时或者对性能非常高的系统(就算做也不会拿c#做)。开发着中小型的web系统,然后叫嚷着”别用反射,性能不行“,那不是扯吗?

    其次,不要因为反射速度比不上直接调用就将它看成毒物,没有了反射,工厂方法怎么办?运行期加载怎么解决?因为没有反射,做出来的系统有可能会因为另外开发原本反射能实现的功能而变得更慢,更难维护。我们不应该因为锤子不能切菜就将锤子骂得一无是处,处处皆不用锤子,要知道,用菜刀来钉水泥钉是很痛苦的。

 

    接着我们来看看反射为什么慢。除了在stackoverflow上那篇 Why is the performance of reflection in C# poor?中别人的回答外,我也看了firelong抨击反射的文章《C#会重蹈覆辙吗?系列之2:反射及元数据的性能问题》和微软CLR程序经理Joel Pobar写的《Dodge Common Performance Pitfalls to Craft Speedy Applications》,都是不错的文章,虽然firelong是持抨击态度,但他说的事实的确可以拿来参考。那么,反射之所以慢,是因为以下几点(照抄firelong的,因为我也是看了他的才知道):

  1. 首先要经过一个绑定过程,非常耗时(用字符串名称和metadata里面的字符串进行比对,字符串查找的算法大家都知道是很慢的操作)。
  2. 然后要进行参数个数、类型等的校验;如果不匹配还要搜索可能的类型转换。
  3. 进行CAS代码访问安全的验证,看允不允许调用。
  4. 以上几个工作,如果不用反射应该是由C#编译器负责在编译时检查的。但是现在如果用反射,全都放到了运行时检查。
  5. 这其中会产生一大堆的临时对象(比如MemberInfo Cache),给垃圾收集器造成巨大负担。

如果有兴趣,可以去查找更多资料。

  再接下来就是,我并不认为反射仅因为这个性能问题就变得一无是处,很多时候不必关心他,对我而言,花时间在优化反射身上,还不如借助反射的特性,而将这些时间花在数据库优化上。

于是,我编写了一些代码来测试一下反射的速度(与直接调用作为对比)。但是对于这样的代码,首先要明确一点就是:肯定不能充分说明问题,因为这些代码只能称为代码片段(code snippet),并不是实际的生产环境的测试结果,而且,由于我对c#很多底层的机制不了解,有可能导致产生误解。所以代码只能作为参考。

我的控制台程序Main方法代码:

static void Main(string[] args)
        {
            //先设置进程和线程的在高优先级,排除调度的影响
            Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
            Thread.CurrentThread.Priority = ThreadPriority.Highest;
            int iteraction = 1000*1000;
            Testing instance = new Testing();
            Type testType = typeof(reflection_performance_testing.Testing);
            Stopwatch stw = new Stopwatch();


            /*----------------------------------------------------------------------------------------------------*/
            ulong cycleStart_dir_new = 0;
            ulong cycleStop_dir_new = 0;
            QueryThreadCycleTime(GetCurrentThread(), ref cycleStart_dir_new);
            stw.Start();
            for (int i = 0; i < iteraction; i++)
            {
                Testing t = new Testing();
            }
            stw.Stop();
            QueryThreadCycleTime(GetCurrentThread(), ref cycleStop_dir_new);
            Console.Write("直接实例化 {0} 个实例总共花费 {1} 毫秒 和 {2} cpu 周期 \n\n\n\n", iteraction, stw.ElapsedMilliseconds, cycleStop_dir_new - cycleStart_dir_new);
            /*----------------------------------------------------------------------------------------------------*/



            /*----------------------------------------------------------------------------------------------------*/
            ulong cycleStart_ref_act = 0;
            ulong cycleStop_ref_act = 0;
            QueryThreadCycleTime(GetCurrentThread(), ref cycleStart_ref_act);
            stw.Reset();
            stw.Start();

            for (int i = 0; i < iteraction; i++)
            {
                Testing t = (Testing)Activator.CreateInstance(testType);
            }
            stw.Stop();
            QueryThreadCycleTime(GetCurrentThread(), ref cycleStop_ref_act);
            Console.Write("重复反射创建(Activator方式)总共花费 {0} 毫秒 和 {1} cpu 周期\n\n\n", stw.ElapsedMilliseconds, cycleStart_ref_act - cycleStop_ref_act);
            /*----------------------------------------------------------------------------------------------------*/


            /*----------------------------------------------------------------------------------------------------*/
            ulong cycleStart_ref_get = 0;
            ulong cycleStop_ref_get = 0;
            QueryThreadCycleTime(GetCurrentThread(), ref cycleStart_ref_get);
            stw.Reset();
            stw.Start();
            for (int i = 0; i < iteraction; i++)
            {
                Testing t = (Testing)testType.GetConstructors()[0].Invoke(null);
            }
            stw.Stop();
            QueryThreadCycleTime(GetCurrentThread(), ref cycleStop_ref_get);
            Console.Write("重复反射(GetConstructor方式)创建 {0} 个实例总共花费 {1} 毫秒 和 {2} cpu 周期 \n\n\n\n", iteraction, stw.ElapsedMilliseconds, cycleStop_ref_get - cycleStart_ref_get);
            /*----------------------------------------------------------------------------------------------------*/


            /*----------------------------------------------------------------------------------------------------*/
            ulong cycleStart_ref_load = 0;
            ulong cycleStop_ref_load = 0;
            QueryThreadCycleTime(GetCurrentThread(), ref cycleStart_ref_load);
            stw.Reset();
            stw.Start();

            for (int i = 0; i < iteraction; i++)
            {
                Assembly ass = Assembly.LoadFrom("AssTesting.dll");
                AssTesting.AssTestingClass t = (AssTesting.AssTestingClass)ass.CreateInstance("AssTesting.AssTestingClass");
            }
            stw.Stop();
            QueryThreadCycleTime(GetCurrentThread(), ref cycleStop_ref_load);
            Console.Write("重复反射创建(Load Assambly方式) {0} 个实例总共花费 {1} 毫秒 和 {2} cpu 周期 \n\n\n\n", iteraction, stw.ElapsedMilliseconds, cycleStop_ref_load - cycleStart_ref_load);
            /*----------------------------------------------------------------------------------------------------*/



            /*----------------------------------------------------------------------------------------------------*/
            ulong cycleStart_dir_invoke = 0;
            ulong cycleStop_dir_invoke = 0;
            QueryThreadCycleTime(GetCurrentThread(), ref cycleStart_dir_invoke);
            stw.Reset();
            stw.Start();
            for (int i = 0; i < iteraction; i++)
            {
                instance.DoSomething();
            }
            stw.Stop();
            QueryThreadCycleTime(GetCurrentThread(), ref cycleStop_dir_invoke);
            Console.Write("重复 {0} 次直接调用方法总共花费 {1} 毫秒 和 {1} cpu 周期 \n\n\n\n", iteraction, stw.ElapsedMilliseconds, cycleStop_dir_invoke - cycleStart_dir_invoke);
            /*----------------------------------------------------------------------------------------------------*/



            /*----------------------------------------------------------------------------------------------------*/
            ulong cycleStart_ref_invoke = 0;
            ulong cycleStop_ref_invoke = 0;
            QueryThreadCycleTime(GetCurrentThread(), ref cycleStart_ref_invoke);
            stw.Reset();
            stw.Start();

            for (int i = 0; i < iteraction; i++)
            {
                testType.GetMethod("DoSomething").Invoke(instance, null);
            }
            stw.Stop();
            QueryThreadCycleTime(GetCurrentThread(), ref cycleStop_ref_invoke);
            Console.Write("重复反射调用方法 {0} 次总共花费 {1} 毫秒 和 {2} cpu 周期 \n\n\n\n", iteraction, stw.ElapsedMilliseconds, cycleStop_ref_invoke - cycleStart_ref_invoke);
            /*----------------------------------------------------------------------------------------------------*/

            Console.Write("全部完毕");
            Console.Read();
        }

其中的Testing类如下:

View Code
class Testing
    {
        public Testing()
        { }

        public Testing(int i)
        { }

        public Testing(string s)
        { }

        public Testing(bool b)
        { }

        public Testing(int i, string s)
        { }

        public Testing(int i, string s, bool b)
        { }

        public void DoSomething()
        {
            //do nothing here..
        }

        public void Method1() { }
        public void Method2() { }
        public void Method3() { }
        public void Method4() { }
        public void Method5() { }
        public void Method6() { }
        public void Method7() { }
        public void Method8() { }
        public void Method9() { }
        public void Method10() { }
        public void Method11() { }
        public void Method12() { }
        public void Method13() { }
        public void Method14() { }
        public void Method15() { }
        public void Method16() { }
        public void Method17() { }
        public void Method18() { }
        public void Method19() { }
        public void Method20() { }

        private int a1;
        private int a2;
        private int a3;
        private int a4;
        private int a5;
        private int a6;
        private int a7;
        private int a8;
        private int a9;
        private int a10;
        private int a11;
        private int a12;

        public int A1
        {
            get { return a1; }
            set { a1 = value; }
        }

        public int A2
        {
            get { return a2; }
            set { a2 = value; }
        }
        
        public int A3
        {
            get { return a3; }
            set { a3 = value; }
        }

        public int A4
        {
            get { return a4; }
            set { a4 = value; }
        }
        
        public int A5
        {
            get { return a5; }
            set { a5 = value; }
        }

        public int A6
        {
            get { return a6; }
            set { a6 = value; }
        }
      
        public int A7
        {
            get { return a7; }
            set { a7 = value; }
        }

        public int A8
        {
            get { return a8; }
            set { a8 = value; }
        }

        public int A9
        {
            get { return a9; }
            set { a9 = value; }
        }

        public int A10
        {
            get { return a10; }
            set { a10 = value; }
        }

        public int A11
        {
            get { return a11; }
            set { a11 = value; }
        }

        public int A12
        {
            get { return a12; }
            set { a12 = value; }
        }
    }
}

项目源码在此

分别测试了10个,100个,1000个,10000个和1000,000(一百万)个实例创建和方法调用。其中一百万个简单类直接实例化/反射实例化和方法直接调用/反射调用的结果如下(我的电脑配置:奔腾双核E5700/4G DDR3 1333 内存/500G 7200转硬盘/ windows 7):

 

【注:我使用了win7的win32 API"QueryThreadCycleTime",XP上好像没有这个函数,需要使用GetThreadTimes,详细请参见:http://www.cnblogs.com/eaglet/archive/2009/03/10/1407791.html

 

    而很多时候,我们开发中的一般系统,也不是十分大规模的使用反射,我曾设想在我的一个ERP系统中使用反射遍历实体类中的所有属性来生成SQL语句,而其中最多属性的一个类中,也只是80个属性,使用反射访问这个类的所有属性,也应该是毫秒级的事情,几十几百毫秒,对于CPU来说,当然慢得可怜,但对我们来说,是不可接受的吗?或许是,但是如果我们使用反射来编写出更好维护,更好的程序,那么对我来说,区区毫秒级的时间绝对可以接受。不过,凡是涉及到技术,特别在开发领域,都会有争论。读者如果有自己的见解,欢迎提出来研究研究。

     最后,祝大家中秋节国庆快乐!!

posted on 2012-09-28 17:43  wyman25  阅读(9776)  评论(26编辑  收藏  举报

导航