Unity GC 優化要點


    整理參考:https://unity3d.com/cn/learn/tutorials/topics/performance-optimization/optimizing-garbage-collection-unity-games (只是看了這篇博客記錄的筆記)

    游戲運行時使用內存來存儲數據,當這些數據不再被使用時,存儲這些數據的內存被釋放以便於之后這些內存可以被復用。

一 核心概念

    垃圾:存儲這些無用數據的內存的術語。

    GC(Garbage Collection):使垃圾(無用內存)可以再次使用的過程。

    需要優化GC的原因:

        ①因為GC的觸發是有條件觸發,當垃圾存儲到一定時候,會被動觸發回收這些無用內存。如果垃圾內過多,被動觸發次數過多,

會造成CPU負荷過多,GC是由CPU啟動的。

        ②每次觸發GC,會有明顯的卡頓,幀率降低,尤其低端手機。如果游戲在戰斗時候發生卡頓,就是很不好的體驗了。

 

二 Unity內存管理

    被釋放:當變量超出作用域時,該內存不再被使用並可以歸還給原來的內存池,當內存被歸還給原有的內存池里。

    被分配:變量在作用域內,分配給他的內存仍然在使用中,我們稱這部分內存已被分配。

    棧:Unity可以訪問的內存池之一,用於短期存儲小塊數據,變量超出作用域時被自動實時釋放。

    堆:Unity可以訪問的內存池之一,用於長期存儲和較大數據塊。變量超出作用域時並沒有被釋放,還是繼續保持被分配狀態。

    垃圾收集器(garbage collector):識別和釋放未使用的堆內存。垃圾收集器定期清理堆。

    棧分配和釋放過程:棧分配和釋放簡單快速。這是因為棧只用於在短時間內存儲小數據。分配和釋放總是以可預測的順序發生,

並具有可預測的大小。棧的工作方式類似於棧數據類型:這是一個簡單的元素集合,這種情況下的內存塊,只能以嚴格的順序添加和刪除元素。這種簡單性和

嚴格性使得它變得非常快速。

    堆分配過程:堆分配比棧分配復雜額多。因為堆可以用來存儲長期和短期數據及各種不同類型大小的數據。分配和釋放並不總是按可預測的順序

進行且可能需要大小差距巨大的內存塊。

    當一個堆內存創建時,將執行以下步驟:

    ①Unity檢查堆上是否有足夠的空間內存,如果有,為該變量被分配內存。如果沒有,Unity觸發GC試圖釋放未使用的堆內存,這個操作可能很慢。

如果GC之后堆內存足夠,則該變量被分配內存 。

    ②GC之后堆上還是沒有足夠的空閑內存,Unity將向操作系統申請更多內存以擴大堆大小。這個操作可能很慢。堆分配可能會很慢,特別在必須執行GC和擴大堆大小時。

              (GC是個費時的操作,堆上的對象越多,代碼中的引用數越多,GC越費時)

 

     GC時的具體的步驟:

      ①垃圾收集器檢索堆上的每個對象、

      ②垃圾收集器搜索所有當前對象引用以確定堆上的對象是否仍在作用域內

      ③不在作用域內的對象唄標記為刪除

      ④刪除被標記的對象並將內存返回給堆。

 

  何時觸發GC:

    堆分配時堆上的可用內存不足時觸發GC。

    GC會自動運行。(頻率因平台而異)

    手動強制調用GC。

三 GC 的問題

    如果堆上有很多對象和大量的對象引用要檢查,則檢查所有這些對象的過程可能很慢。這可能導致游戲卡頓或緩慢運行。

    GC在不合時宜的場合被觸發。如果CPU在我們游戲的性能關鍵部分已經滿負荷了,那此時即使是少量的GC額外開銷也可能導致

我們的游戲卡頓或運行緩慢。

    堆碎片,當從堆中分配內存時,會根據必須存儲的數據大小從不同大小的塊中的可用空間中獲取內存。

當這些內存返回到堆時,堆可能分成很多由分配塊分隔的小空閑塊.這意味着雖然可用內存總量很高。但是由於

碎片化嚴重而無法分配一塊連續的大內存。這意味着GC被觸發或不得不擴大堆大小。

(嚴重后果:①游戲內存大小會高於實際所需要的大小 ②GC會被頻繁的觸發)

四 優化GC(減少GC的次數)

      ①嘗試在合適時機(loading時),手動觸發GC和擴展堆大小以便GC可控。

      ②緩存,將局部函數中的局部引用變量寫成公共。

      ③對象池。

      ④清理容器。

      ⑤字符串的創建之類。

      ⑥Debug.Log的引用。

      ⑦注意裝箱,裝箱會產生垃圾源於底層,當一個值類型變量被裝箱時,Unity在堆上創建一個臨時的System.Object

來包裝值類型變量。一個System.Object是一個引用類型的變量,所以當這個臨時對象被處理時會產生垃圾。

      ⑧StartCortine會產生少量垃圾。

        yield return 0;//會產生垃圾,int變量0被裝箱。

        =>>> yield return null

        yield return new WairforSeconds(1f);//如果多次被調用,會產生很多。

        ==>>>WaitForSeconds delay =new WaitForSeconds(1f);//可以事先緩存起來

           yield return delay;

      ⑨Linq和正則表達式在后台有裝箱操作而產生垃圾,最好少使用。

      ⑩構建代碼以最小化GC的影響

        代碼的構建方式可能會影響GC,即使代碼中沒有堆分配,也可能會增加GC的負擔。可能增加GC的負擔之一

是要求檢查他不該檢查的東西。Struct是值類型變量,但是如果包含一個引用類型便利的struct,那么垃圾收集器必須檢查整個

結構體。

        另外一個增加GC負擔的操作是使用不必要的對象引用,當垃圾收集器搜索堆上對象的引用時,它必須檢查代碼中的

每個當前對象的引用。更少的對象引用意味着更少的工作量。

 


免責聲明!

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



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