C# 程序性能提升篇-1、裝箱和拆箱,枚舉的ToString淺析


前景提要:

  編寫程序時,也許你不經意間,就不知不覺的使程序代碼,發生了裝箱和拆箱,從而降低了效率,不要說就發生那么一次兩次,如果說是程序中發生了循環、網絡程序(不斷請求處理的)等這些時候,減少裝箱和拆箱,是優化程序提高效率的一種途徑。不僅跬步,無以至千里,不積小流,無以至江河。優化從點點滴滴做起。

一、裝箱拆箱概念:

  這里是官方定義:http://msdn.microsoft.com/zh-cn/library/yz2be5wk.aspx

  裝箱:值類型→引用類型

  拆箱:引用類型→值類型

二、為什么說裝箱,拆箱消耗資源(內存、cpu)?

  2.1 圖說裝箱、拆箱

                         

    說明:裝箱。值類型存放於內存棧上,引用類型存放於內存對上。如果將已定義好的值類型(棧上的數據)修改至引用類型(堆上),

  2.2 圖文說 裝箱過程

    值類型存儲(沒有堆什么事):                             引用類型存儲(棧中存儲的是,堆中對象的地址,堆中是實際對象)

          

    這時如果,將值類型變成引用類型,存儲的位置發生變化,發生了裝箱,而且為了拆箱,現在引用類型的存儲模式也不僅僅是以上引用類型的存儲模型了,值類型的類型也會進行相應的存儲,以方便在拆箱時候,轉換成相應裝箱時的類型。

    這樣可以看出,裝箱,其實比你直接定義成一個引用類型,更加消耗了內存,以及增加了計算量(消耗了cpu)。這是原理級別的解釋,跟深入的,我也不太清除。只能分析到CLR這一步。

三、淺談ToString()

  估計大家都知道,C#所有的類型基類(父類)均是Object,而Object中,提供的能叫子類繼承的方法就那么幾個,virtual 的ToString就是其中之一,所以說,c#中所有的類型均有這個ToString方法。下面就淺談一下ToString方法在裝箱拆箱中的一二。

  3.1 針對普通值類型

    以Int32為例(Struct)

      int a=123;
      string b=a.ToString();

        請問這是發生裝箱了嗎?

      答:值類型→引用類型,oh,裝箱!!

      解答:只單純的看裝箱定義,這確實符合裝箱的定義。但是,別忘記了ToString是基類的虛方法,子類是否對其有重寫。

        int 的 戶口祖籍

        int(C#語言)→Int32(CLR,oh是個結構,struct

          →extends System.ValueType(查看IL代碼,發現了)→extends System.Object(這是終極祖宗啊!這里有ToString啊)

        這是Int32中對ToString方法的重寫:

            public override string ToString() { return Number.FormatInt32(this, null, NumberFormatInfo.CurrentInfo);}

        接下來就是內部的實現了,我去,看不到了啊?怎么辦?

      對了編寫代碼,查看IL代碼。

      

      可以看出這里沒有發生裝箱啊!具體的深入內部實現可以借助反編譯工具,查看,如ILSpy、reflecter、ILdasm等。

  

  3.2枚舉類型

    那么所有的值類型是不是使用Tostring方法,均不涉及裝箱操作呢?這個也不盡然,可是嘗試一下枚舉類型。

    枚舉類型,是一個值類型。

    示例:

      enum TestEnum { Test1, Test2   }

      string test = TestEnum.Test1.ToString(); //這句話是否發生裝箱操作

  

    3.2.1 內部原理

      首先查看枚舉中的ToString方法,這里重寫了ToString方法

      public override string ToString() { return InternalFormat((RuntimeType) base.GetType(), this.GetValue());}

      查看InternalFormat方法的實現

        private static string InternalFormat(RuntimeType eT, object value)
        {
            if (eT.IsDefined(typeof(FlagsAttribute), false))
            {
                return InternalFlagsFormat(eT, value);
            }
            string name = GetName(eT, value);
            if (name == null)
            {
                return value.ToString();
            }
            return name;
        }

  

       通過查看可知eT.IsDefined(typeof(FlagsAttribute), false)、GetName(),這里使用了反射,可能會有性能的損失,但是不會有裝箱操作

       但GetName(eT,value),中的value參數是InternalFormat中的參數,這里的參數是object類型,

        而InternalFormat((RuntimeType) base.GetType(), this.GetValue())調用時,這里的使用了this.GetValue這個方法來傳遞這個object參數

       接下來查看 GetValue方法的實現啦

        

      可以看出關於這個GetValue方法中發生了,裝箱操作,return  (bool) *(((sbyte*) ptrRef)); 這個一個值類型,而GetValue需要的返回值是:Object類型

    結論,枚舉中重寫的ToString方法不僅使用到了裝箱操作,而且還是用到了大量的反射。

    綜上所述,使用枚舉時,只是針對值類型操作,增加幾個常量狀態switch-case,以及不涉及取出枚舉定義的值(ToString)則是非常方便的,快速的。

      但是要是經常使用枚舉的ToString取得枚舉的定義值,則不建議使用。這里是非常不合時宜的。可以直接使用靜態類代替即可(使用空間換取時間)

  3.3 分析網絡大牛的技術博客

    原本裝箱、拆箱感覺寫的差不多了,但是看到網上那么多大牛、那么寫感覺有點不合適啊!(不要被他們所謂的比較性能嚇到哦)

    3.3.1 博客地址:http://www.cnblogs.com/XmNotes/archive/2010/09/18/1830355.html

      這是第一個:性能相差7千倍的ToString方法  的博客

      解說:看到標題,第一句想說的是,我靠!這么雷人啊。7千倍啊!

        但是一看代碼你就知道他在干嘛了

          var day = DayOfWeek.Wednesday; //這可是枚舉啊

          for (int i = 0; i < 1000000; i++)    {        value = day.ToString();    }

        百萬級別的反射、裝箱。你坑人呢吧,不說實際有沒有這么百萬級別的數量和這么頻繁的操作,就說有你這么用的嗎!

          一種是你直接返回一個值類型的星期,最后表現層給你轉換一下,即使這里裝箱、拆箱也就是這么一次兩個,還能百萬級別的刷啊!

          還有就是類似你的第二種,直接就是操作引用類型的,如果像你這樣百萬級別的在轉換一下,弄成靜態常量。 

       結論是:舉例要以事實做依據,不要做不符合實際的事情。不同的方法、類庫用於適合的場景。這里不僅僅反射會耗時,裝箱操作也會造成資源的消耗

    3.3.2 博客地址:http://www.cnblogs.com/yjmyzz/archive/2010/09/19/1830766.html

      這是第二個:也談枚舉ToString()性能的改進 的博客

      解說:我不理解樓主在干嗎,你定義的靜態類,在第一次使用的時候,就已經將枚舉裝到靜態變量dictionary中了,常駐內存了,直到程序結束才推出。

          類似於你沒事循環讀取百萬級別的一個靜態變量啊!

         而使用枚舉的ToString方法,是你在百萬級別的反射、裝箱數據啊!

        我暈啊!枚舉是這樣用,這樣理解的嗎?

        如果這幾個定義你常用、百萬級別讀取的話,你能不能稍微浪費點內存啊!直接這樣用啊(空間換取時間)

          public static class EnumLoginError
          {
              public static string 用戶名不存在 { get{return "用戶名不存在";}}
              public static string 密碼錯誤 { get{return "密碼錯誤";}}
              public static string 用戶被鎖定 { get{return "用戶被鎖定";}}
              public static string 未知錯誤 { get{return "未知錯誤";}}
          }
      結論是:不要做畫蛇添足的事情,對待事物要有懷疑精神。還是合適的工具做合適的活,合適的人做合適事情。

四、能夠減少拆箱裝箱,常用的替代類庫

  4.1 推薦使用泛型集合

    命名空間System.Collections.Generic
    List<T>類似於ArrayList,ArrayList的升級版。
      各種方法:Sort()、Max()、Min()、Sum()…
    Dictionary<K,V>類似於Hashtable,Hashtable的升級版。
    T,K,V就像一把鎖,鎖住集合只能存某種特定的類型,這里的T,K,V也可以是其它字母

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM