Unity3D為我們提供了一個強大的性能分析工具Profiler。今天我們就使用Profiler來詳細分析一下官方例子AngryBots的內存使用信息數據。
首先打開Profiler選擇Memory選項,在游戲運行的某一幀查看Detailed選項數據(Simple模式的數據很直觀,可以知道內存大體被哪部分占用了),如下圖所示:
選中后,unity會自動獲取這一幀的內存占用數據項,主要分為:Other、Assets、BuiltinResources、Scene Memory、NotSaved這五大部分,下面我們就來一一分析。
-
Other
記錄數據項很多,篇幅時間有限,我們就專挑占用大小排行榜靠前的幾項來詳細分析吧。
- 統可執行程序和DLL,是只讀的內存,用來執行所有的腳本和DLL引用。不同平台和不同硬件得到的值會不一樣,可以通過修改Player Setting的Stripping Level來調節大小。
Ricky:我試着修改了一下Stripping Level似乎沒什么改變,感覺雖占用內存大但不會影響游戲運行。我們暫時忽略它吧(- -)!
- GfxClientDevice:GFX(圖形加速\圖形加速器\顯卡 (GraphicsForce Express))客戶端設備。
Ricky:雖占用較大內存,但這也是必備項,沒辦法優化。繼續忽略吧(- -)!!
- ManagedHeap.UsedSize:托管堆使用大小。
Ricky:重點監控對象,不要讓它超過20MB,否則可能會有性能問題!
- ShaderLab:Unity自帶的着色器語言工具相關資源。
Ricky:這個東西大家都比較熟悉了,忽略它吧。
- SerializedFile:序列化文件,把顯示中的Prefab、Atlas和metadata等資源加載進內存。
Ricky:重點監控對象,這里就是你要監控的哪些預設在序列化中在內存中占用大小,根據需求進行優化。
- PersistentManager.Remapper:持久化數據重映射管理相關
Ricky:與持久化數據相關,比如AssetBundle之類的。注意監控相關的文件。
- ManagedHeap.ReservedUnusedSize:托管堆預留不使用內存大小,只由Mono使用。
Ricky:無法優化。
- Assets
- Texture2D: 2D貼圖及紋理。
Ricky:重點優化對象,有以下幾點可以優化:
- 許多貼圖采用的Format格式是ARGB 32 bit所以保真度很高但占用的內存也很大。在不失真的前提下,適當壓縮貼圖,使用ARGB 16 bit就會減少一倍,如果繼續Android采用RGBA Compressed ETC2 8 bits(iOS采用RGBA Compressed PVRTC 4 bits),又可以再減少一倍。把不需要透貼但有alpha通道的貼圖,全都轉換格式Android:RGB Compressed ETC 4 bits,iOS:RGB Compressed PVRTC 4 bits。
- 當加載一個新的Prefab或貼圖,不及時回收,它就會永駐在內存中,就算切換場景也不會銷毀。應該確定物體不再使用或長時間不使用就先把物體制空(null),然后調用Resources.UnloadUnusedAssets(),才能真正釋放內存。
- 有大量空白的圖集貼圖,可以用TexturePacker等工具進行優化或考慮合並到其他圖集中。
- AudioManager:音頻管理器
Ricky:隨着音頻文件的增多而增大。
- AudioClip:音效及聲音文件
Ricky:重點優化對象,播放時長較長的音樂文件需要進行壓縮成.mp3或.ogg格式,時長較短的音效文件可以使用.wav 或.aiff格式。
- Cubemap:立方圖紋理
Ricky:這個一般在天空盒中比較常見,我也不知道如何優化這個。。。
- Mesh:模型網格
Ricky:主要檢查是否有重復的資源,還有盡量減少點面數。
- Scene Memory
- Mesh:場景中使用的網格模型
Ricky:注意網格模型的點面數,能合並的mesh盡量合並。
- Builtin Resources
Ricky:這些都是Unity的一些內部資源,對於項目內存沒有什么分析價值,所以我就暫時不對其進行分析了。
- Profiler內存重點關注優化項目
1)ManagedHeap.UsedSize: 移動游戲建議不要超過20MB.
2)SerializedFile: 通過異步加載(LoadFromCache、WWW等)的時候留下的序列化文件,可監視是否被卸載.
3)WebStream: 通過異步WWW下載的資源文件在內存中的解壓版本,比SerializedFile大幾倍或幾十倍,不過我們現在項目中展示沒有。
4)Texture2D: 重點檢查是否有重復資源和超大Memory是否需要壓縮等.
5)AnimationClip: 重點檢查是否有重復資源.
6)Mesh: 重點檢查是否有重復資源.
- 項目中可能遇到的問題
1.Device.Present:
1)GPU的presentdevice確實非常耗時,一般出現在使用了非常復雜的shader.
2)GPU運行的非常快,而由於Vsync的原因,使得它需要等待較長的時間.
3)同樣是Vsync的原因,但其他線程非常耗時,所以導致該等待時間很長,比如:過量AssetBundle加載時容易出現該問題.
4)Shader.CreateGPUProgram:Shader在runtime階段(非預加載)會出現卡頓(華為K3V2芯片).
5)StackTraceUtility.PostprocessStacktrace()和StackTraceUtility.ExtractStackTrace(): 一般是由Debug.Log或類似API造成,游戲發布后需將Debug API進行屏蔽。
2.Overhead:
1)一般情況為Vsync所致.
2)通常出現在Android設備上.
3.GC.Collect:
原因:
1)代碼分配內存過量(惡性的)
2)一定時間間隔由系統調用(良性的).
占用時間:
1)與現有Garbage size相關
2)與剩余內存使用顆粒相關(比如場景物件過多,利用率低的情況下,GC釋放后需要做內存重排)
4.GarbageCollectAssetsProfile:
1)引擎在執行UnloadUnusedAssets操作(該操作是比較耗時的,建議在切場景的時候進行)。
2)盡可能地避免使用Unity內建GUI,避免GUI.Repaint過渡GCAllow.
3)if(other.tag == a.tag)改為other.CompareTag(a.tag).因為other.tag為產生180B的GC Allow.
4)少用foreach,因為每次foreach為產生一個enumerator(約16B的內存分配),盡量改為for.
5)Lambda表達式,使用不當會產生內存泄漏.
5.盡量少用LINQ:
1)部分功能無法在某些平台使用.
2)會分配大量GC Allow.
6.控制StartCoroutine的次數:
1)開啟一個Coroutine(協程),至少分配37B的內存.
2)Coroutine類的實例 -> 21B.
3)Enumerator -> 16B.
7.使用StringBuilder替代字符串直接連接.
8.緩存組件:
1)每次GetComponent均會分配一定的GC Allow.
2)每次Object.name都會分配39B的堆內存.