1. 程式人生 > >Unity優化大全(四)之CPU-GC(記憶體回收)和Sricpt

Unity優化大全(四)之CPU-GC(記憶體回收)和Sricpt

前言:

       對於GC,大家可能不陌生把,也就是記憶體回收。同時筆者在做自己的小遊戲中發現很多細節都會影響GC,現在就給大家梳理下一些需要注意的地方。

進入主題:

       在說CPU優化時,談起GC是不是覺得很奇怪?其實筆者不這麼覺得,雖然GC是用來處理記憶體回收的,但是卻增加了CPU的開銷。因此對於GC的優化目標就是儘量少的觸發GC。

       首先我們要知道所謂的GC是Mono執行時的機制,而非Unity3D遊戲引擎的機制,所以GC也主要是針對Mono的物件來說的,而它管理的也是Mono的託管堆。 明白了這一點,你也就明白了GC不是用來處理引擎的Assets(貼圖,音效,模型等等)的記憶體釋放的,因為U3D引擎也有自己的記憶體堆而不是和Mono一起使用所謂的託管堆。

       其次我們還要清楚什麼東西會被分配到託管堆上?對,就是引用型別。引用型別包括:使用者自定義的類,介面,委託,陣列,字串,Object.而值型別包括:幾種基本資料型別(如:int,float,bool等),結構體,列舉,空型別。所以我們我們就應該儘量不要區使用那些引用型別。

那麼GC什麼時候會觸發呢?兩種情況:

  1.  當我們的堆的記憶體不足時,會自動呼叫GC來回收記憶體。
  2.  我們自己也可以手動的呼叫GC,用System.GC.Collect(),一般情況下,不建議手動去手動進行記憶體回收,因為  容易出現問題的!

       因此為了達到優化CPU的目的,我們就不能頻繁的觸發GC。而上文也說了GC處理的是託管堆,而不是Unity3D引擎的那些資源,其實說白了GC的優化也就是程式碼的優化。一下有事隔熱你總結有些是參考了網上寫的不錯的文章。具體需要注意的地方如下:

注意:

  1. 字串連線的處理。因為將兩個字串連線的過程,其實是生成一個新的字串的過程。而之前的舊的字串自然而然就成為了垃圾。而作為引用型別的字串,其空間是在堆上分配的,被棄置的舊的字串的空間會被GC當做垃圾回收,可以使用StringBuilder來解決(注意:C#沒有StringBuffer,Java裡才有!!String 在進行運算時(如賦值、拼接等)會產生一個新的例項,而 StringBuilder 則不會。所以在大量字串拼接或頻繁對某一字串進行操作時最好使用 StringBuilder,不要使用 String
  2. 儘量不要使用foreach,而是使用for。foreach其實會涉及到迭代器的使用,而據傳說每一次迴圈所產生的迭代器會帶來24 Bytes的垃圾。那麼迴圈10次就是240Bytes。
  3. 不要直接訪問gameobject的tag屬性。比如if (go.tag == “human”)最好換成if (go.CompareTag (“human”))。因為訪問物體的tag屬性會在堆上額外的分配空間。如果在迴圈中這麼處理,留下的垃圾就可想而知了。
  4. 不要例項化(Instantiate)和(Destory)物件,事先建好物件池以實現空間的重複利用,大家可以看看我已經的ObjectPool這篇文章就懂了。
  5. 不使用LINQ命令,因為它們一般會分配中間緩器,而這很容易生成垃圾內
  6. 避免分配記憶體,您應該避免分配新物件,除非你真的需要,因為他們不再在使用時,會增加垃圾回收系統的開銷。您可以經常重複使用陣列和其他物件,而不是分配新的陣列或物件。這樣做好處則是儘量減少垃圾的回收工作。同時,在某些可能的情況下,您也可以使用結構(struct)來代替類(class)。這是因為,結構變數主要存放在棧區而非堆區。因為棧的分配較快,並且不呼叫垃圾回收操作,所以當結構變數比較小時可以提升程式的執行效能。但是當結構體較大時,雖然它仍可避免分配/回收的開銷,而它由於"傳值"操作也會導致單獨的開銷,實際上它可能比等效物件類的效率還要低。他們有如下區別:

(1) 類在傳遞的時候,傳遞的內容是位於託管記憶體中的位置,結構體在傳遞的時候,傳遞的內容是位於程式堆疊區的內容。當類的傳遞物件修改時,將同時修改源物件,而結構體的傳遞物件修改時,不會對源物件產生影響。

    (2)在一個類中,可以定義預設的、不帶引數的建構函式,而在結構體中不能定義預設的、不帶引數的建構函式。兩者都可以定義帶有引數的建構函式,通過這些引數給各自的欄位賦值或初始化。

    (3)類支援繼承類,結構體不支援結構體但是可以繼承介面。

(4)繼承的基類不同,但都是從object派生的 。

(5)類成員在沒有修飾符情況下預設私有(我記得Java好像是default,那是我還被老師提問來著,呵呵!),結構體預設public。詳情如下

(如果一個類的成員沒有任何許可權修飾,那麼它門就是預設包訪問許可權,也就是同一個 包內其它類可以訪問,但包外就不可以。對於同一個資料夾下的、沒有用package的classes,Java會自動將這些classes初見為隸屬於該目錄的default下)。

7.儘量不要再Update函式中做複雜計算,如有需要,可以隔N幀計算一次,比如:

<span style="font-family:Arial;font-size:14px;">void Update(){ if(Time.frameCount%8==0) { DoSomething(); }}  </span>
8.在使用陣列或ArrayList物件時應當注意,快取其長度:
<span style="font-family:Arial;font-size:14px;">length=myArray.Length;  </span>
9.定時重複呼叫可以使用InvokeRepeating函式實現,比如,啟動1秒後每隔1秒執行一次 DoSomeThing 函式:
<span style="font-family:Arial;font-size:14px;">void Start()   
{        
 InvokeRepeating("DoSomeThing", 0.5f, 1.0f);  
} </span>
10.少使用臨時變數,特別是在Update OnGUI等實時呼叫的函式中。
<span style="font-family:Arial;font-size:14px;">void Update()  
{  
   Vector3 post;  
   post=transform.position;  
}  </span>
可改為:
<span style="font-family:Arial;font-size:14px;">private Vector3 post;  
void Update()  
{  
   post=transform.position;  
}  </span>

11.Cache一些東西,在update裡面儘量避免search,如GameObject.FindWithTag("")、GetComponent這樣的呼叫,可以在Start中預先存起來。

12.儘量減少函式呼叫棧,用x = (x > 0 ? x : -x);代替x = Mathf.Abs(x).

13.不要使用SendMessage之類的方法,他比直接呼叫方法慢了100倍,你可以直接呼叫或通過C#的委託來實現。

14.使用javascript或Boo語言時,你最好確定變數的型別,不要使用動態型別,這樣會降低效率,你可以在指令碼開頭使用#pragmastrict 來檢查,這樣當你編譯你的遊戲時就不會出現莫名其妙的錯誤了。比如:

<span style="font-family:Microsoft YaHei;font-size:14px;">function Start ()
{
    var foo = GetComponent(MyScript);
    foo.DoSomething();
}</span>

這裡foo將是動態型別,因此呼叫函式DoSomething必須要較長的時間,因為foo的型別未知,它必須弄明白是否支援DoSomething函式,如果支援,呼叫函式。
<pre name="code" class="javascript"><span style="font-family:Arial;font-size:14px;"><span style="font-family:Microsoft YaHei;font-size:14px;"><pre name="code" class="javascript">function Start ()
{
    var foo : MyScript = GetComponent(MyScript);
    foo.DoSomething();
}
</span></span>
這裡我們強制foo為指定型別,你將獲得更好的效能。

15.優化數學運算,儘量避免使用float,而使用int,特別是在手機遊戲中,儘量少用複雜的數學函式,比如sin,cos等函式。改除法/為乘法,例如:使用x*0.5f而不是 x/2.0f 。

16.壓縮 Mesh  。匯入 3D 模型之後,在不影響顯示效果的前提下,最好開 Mesh Compression。  Off, Low, Medium, High 這幾個選項,可酌情選取。對於單個Mesh最好使用一個材質。

 17.刪除空的Update方法。當通過Assets目錄建立新的指令碼時,腳本里會包括一個Update方法,當你不使用時刪除它。

18.使用內建陣列,內建陣列是非常快的。ArrayList或Array類很容易使用,你能輕易新增元件。但是他們有完全不同的速度。 內建陣列有固定長度,並且大多時候你會事先知道最大長度然後填充它。內建陣列最好的一點是他們直接嵌入結構資料型別在一個緊密的快取裡,而不需要任何額外 型別資訊或其他開銷。因此,在快取中遍歷它是非常容易的,因為每個元素都是對齊的。比如: Vector3.zero; 

19.不要使用原生的GUI方法,用NGUI代替最好!

20.需要隱藏/顯示或例項化來回切換的物件,儘量不要使用SetActiveRecursively或active,而使用將物件遠遠移出相機範圍和移回原位的做法或者使用OnBecameVisible()和OnBecameVisible()

21.只在一個指令碼中使用OnGUI方法;

22.對於方法的引數的優化:善於使用ref關鍵字。值型別的引數,是通過將實參的值複製到形參,來實現按值傳遞到方法,也就是我們通常說的按值傳遞。複製嘛,總會讓人感覺很笨重。比如Matrix4x4這樣比較複雜的值型別,如果直接複製一份新的,反而不如將值型別的引用傳遞給方法作為引數。

     ..........

最後的最後:

   感覺CPU的GC Script的優化方面會很多,暫時先暫時現總結這麼點,之後我會不斷補充的,如有更好的優化方面,可以在下面留言哦,謝謝咯。奮鬥