※本文不是在描述舊版本Unity中mono編譯器致使的foreach語句額外裝箱錯誤框架
博主是一名Unity 3D遊戲開發者,遊戲使用C#+lua開發,最近在優化C#代碼時,發現了一處使用foreach不恰當的地方,其結果是形成了每幀近3k的GC Alloc,如此高頻率的GC堆內存分配,會致使垃圾回收的調用更加頻繁,從而影響遊戲性能,而這隻須要簡單的修改便可避免;性能
※使用.Net 2.0的Unity版本,若是是較新的.Net 4.x版本,因爲FCL實現修改,本文中48->56優化
原始聲明代碼以下:this
private readonly IDictionary<int, MyClass> mDic = new Dictionary<int, MyClass>();
在每幀邏輯裏面,會屢次對其進行遍歷,遍歷代碼以下:lua
foreach(var keyValuePair in mDic) { //do... }
經過Unity自帶的Profiler分析,能夠發現其致使的GC Alloc:spa
經過上面的Profiler能夠發現此時foreach語句實際上調用的是Dictionary定義中隱式實現的IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()方法,該方法的聲明以下:code
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() { return new Enumerator(this, Enumerator.KeyValuePair); }
其中,Enumerator是在Dictionary類中定義的嵌套結構類型:對象
結構類型隱式轉換爲接口類型時會發生裝箱,對於該Enumerator類型,其裝箱後大小爲16(開銷字節)+8(字段dictionary)+8(字段next+stamp)+16(字段current:8(int字節對齊)+8)=48字節,調用29次即產生48*29=1392字節的堆內存分配,這符合咱們看到Profiler裏面看到的GC Alloc;blog
爲了解決這個問題,只須要將變量聲明時改成Dictionary便可,不使用接口類型的變量,即:接口
private readonly Dictionary<int, MyClass> mDic = new Dictionary<int, MyClass>();
此時,在對mDic進行foreach循環時,就會調用Dictionary<TKey,TValue>.GetEnumerator()方法,該方法返回值類型即結構類型的Enumerator,避免了裝箱操做:
這裏可能不少人有個誤解,即foreach是隻能對實現了IEnumerable或IEnumerable<T>的類型對象進行遍歷,其實否則,foreach語句還能夠對知足如下條件的任何類型的對象進行遍歷:
實現了可訪問的GetEnumerator()方法,且該方法的返回值類型符合:包含可訪問的Current屬性和bool MoveNext()方法;
這樣就知道了,.Net框架類庫提供的泛型集合類型都實現了這樣的方法,所以能夠放心對泛型集合進行foreach遍歷,而不產生堆內存的分配,也所以,咱們在使用這些類型時,儘可能避免直接對其接口類型的變量進行遍歷;
若是您以爲閱讀本文對您有幫助,請點一下「推薦」按鈕,您的承認是我寫做的最大動力!
做者:Minotauros
出處:https://www.cnblogs.com/minotauros/
本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利。