使用Unity製作遊戲也好一陣子,
途中踩過不少坑都是對於Unity預設值與C#程式語言的設計上的不熟悉,
這次想探討的是關於C#程式語言的設計方面,
特別是相對於C/C++這些低階語言來說,C#/Java的記憶體分配像是一個巨大的黑盒子,
運作起來還蠻順利的,但是時不時就會像樂高一樣踩到腳(而且很痛),
這篇就來記錄一下自己學到的東西,順便討論裝箱(Boxing)跟C#的記憶體運作吧!
一、C#的記憶體分配由於看到文章的朋友,可能不一定對程式語言的一些處理非常孰悉,
所以在這邊先簡單整理關於C#記憶體分配的一些規則。
對C#來說,一個Class分成三種類型:
1. Value Type 2. Reference Type 3. Pointer Type
其中第三項要開啟unsafe才能夠使用,而Unity的預設值是關閉的,所以可以先不理它。
前兩項Value Type跟Reference Type,在MSDN的官方說明上是這樣的:
Value types are either stack-allocated or allocated inline in a structure.
Reference types are heap-allocated
可以了解到,
對於Value Type來說,一般是存放在STACK段,做為區域變數使用。
對於Reference Type來說,一般是存放在HEAP段,由C#的Garbage Collection控管。
簡單來說:
Value Type就是一般常用的int, float, char, struct, enum這種簡單的資料型態,
他們在一般情況通常是區域變數,也就是用完就丟的,對記憶體的消耗也不會太大。
Reference Type就是利用class包裝起來的資料了,他們會被整包放到動態記憶體裡面,
如果不特別處理,就會持續占用記憶體,當相關的程式使用完這些變數,
C#就會利用Garbage Collection的方法,回收這些用不到的記憶體,
在Collect的當下會消耗較多CPU資源,甚至在手機上有可能會導致掉幀的情況,
所以我們一般會希望迴避這種動態記憶體的分配能夠有所節制,
也是前幾篇文章所提到的物件池存在的一部份原因。
二、萬惡的Boxing跟Unboxing
介紹完兩種C#的記憶體分配方式之後,再回頭討論標題的Boxing究竟是什麼意思吧。
在寫遊戲程式的時候,常常會遇到一種狀況:
「需要把現在的數值記錄下來,做為某種用途」
這種情況,我們常常會宣告一個class,像是下面這種存檔用的資訊:
在使用上,我們通常會寫成:
這種形式,此時原本存在區域變數的level跟health,就需要複製一份到動態變數區,
並且包裝成SaveFile的形式一直存在於動態變數區,直到它被使用完畢,被GC給回收去。
這樣從區域變數(Value Type)被打包到動態變數區段(HEAP段),就被稱為Boxing(裝箱)。
反過來從動態變數區段擷取成區域變數(Value Type)就被稱為Unboxing(拆箱)。
三、總結
其實Boxing跟Unboxing在寫程式的時候幾乎是難以完全避免的,
我們只要記得盡量少使用裝拆箱,以及在使用ArrayList, HashTable這種
一放進去就會被轉型成object type的自動裝箱結構時,要務必小心使用。
另外就是盡量在不忙碌的時候(打開UI選單、暫停遊戲、或讀取關卡時),
手動呼叫System.GC.Collect()進行手動GC,勢必可以稍微減少GC回收對效能造成的影響囉!
參考資料: