程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> C# 程序性能提升篇-1、裝箱和拆箱,枚舉的ToString淺析

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

編輯:C#入門知識

前景提要:     編寫程序時,也許你不經意間,就不知不覺的使程序代碼,發生了裝箱和拆箱,從而降低了效率,不要說就發生那麼一次兩次,如果說是程序中發生了循環、網絡程序(不斷請求處理的)等這些時候,減少裝箱和拆箱,是優化程序提高效率的一種途徑。不僅跬步,無以至千裡,不積小流,無以至江河。優化從點點滴滴做起。   一、裝箱拆箱概念:     這裡是官方定義: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也可以是其它字母

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved