http://blog.csdn.net/lodypig/article/details/51879702
AssetBundle內存占用
先上圖,Don’t panic。 
我們從AssetBundle中加載資源一般會經過三個步驟:
- www、LoadFromFile、LoadFromMemory等接口加載AssetBundle本身。
- 通過AssetBundle.LoadAsset()等接口從AssetBundle中加載資源。
- 對於GameObject類資源,還需通過GameObject.Instantiate()創建clone。
這幾個步驟,都會產生內存分配,並且分別對應了圖中不同顏色的部分。而圖的左邊已經寫出了產生內存分配的接口,右邊則標明了釋放內存的方法。
接下來詳細看看每部分的含義。
-
黑色部分:www類本身占用內存,通過www接口加載AssetBundle才會有這部分內存,www對象保留了一份對WebStream數據(粉色部分)的引用。使用www =null 或者 www.dispose()釋放。其中www.dispose()會立即釋放,而www = null會等待垃圾回收。釋放www后WebStream的引用計數會相應減一。
-
橙色部分:官方稱為WebStream數據,是數據真正的存儲區域。當AssetBundle被加載進來后,這部分內存就被分配了。它包含3個內容:壓縮后的AssetBundle本身、解壓后的資源以及一個解壓緩沖區(圖綠)。無論www(黑色部分)還是后面會提到的AssetBundle對象(粉色部分),都只是有一個結構指向了WebStream數據,從而能對外部提供操作真正資源數據的方法。而當www對象和AssetBundle對象釋放時,WebStream數據的引用計數也會相應減1。當WebStream數據引用計數為0時,系統會自動釋放。但為了不頻繁地開辟和銷毀解壓Buffer,其中綠色Decompression解壓緩沖區Unity會至少保留一份。例如同時加載3個AssetBundle時,系統會生成3個Decompression Buffer,當解壓完成后,系統會銷毀兩個。
-
粉色部分:AssetBundle對象,引用了橙色WebStream數據部分,並提供了從WebStream數據中加載資源的接口。通過AssetBundle.Unload(bool unloadAllLoadedObjects)釋放。如果調用AssetBundle.Unload(false),將釋放AssetBundle對象本身,其對WebStream引用也將減少,從而可能引起WebStream釋放,我們也就無法再通過接口或依賴關系從該AssetBundle加載資源。但已加載的資源還可以正常使用。如果調用的是AssetBundle.Unload(true),不僅會釋放WebStream部分,所有被加載出來的資源將被釋放。無論true或false,AssetBundle.Unload()都將銷毀AssetBundle,銷毀后調用該AssetBundle對象的任何方法都不會生效或產生報錯,也就是說這個接口只能被調用一次,不能先調用unload(false)再調用unload(true)。
-
紅色部分:上圖中紅色部分不是非常清楚,我們換一張圖:
這里紫色區域”AssetBundle文件內存鏡像”就是上面的WebStream和www(黑橙)部分,虛線里的資源就是從AssetBundle加載出來的原始資源了,也屬於WebStream范圍,我們不再討論。
而綠色實線內的資源(對應第一張圖紅色部分),也就是就是我們通過Instantiate()創建的GameObject所包含的資源。這些包含的資源又根據類型,與AssetBundle原始資源(WebStream資源部分)有不同的關系。有些如Texture、shader資源,我們通常只是使用,並不會對其做出改動,所以僅僅是引用關系;而每個GameObject都是特殊的,所以是完全復制了一份;至於Mesh和Material,則是引用+復制的關系。
建議
- www加載完畢后調用www.dispose()或www = null。
- 對於shader、通用圖集等常駐且需要保留依賴關系的資源,在合適時機加載進來,不調用AssetBundle.Unload()接口。
- 其他根據不同使用情況,選擇AssetBundle.Unload()策略。
- 對於界面等存在明確生命周期,又可能動態加載的資源,在生命周期結束后調用AssetBundle.Unload(true),將全部資源一起釋放。
- 對於加載完后不再需要主動和被動依賴加載的資源,在加載完成后調用AssetBundel.Unload(false),立即釋放掉AssetBundle資源,當資源使用完畢,代用Resources.UnloadAsset()或Resources.UnloadUnusedAssets()釋放資源內存。
- Resources.UnloadUnusedAssets()有較大消耗,盡量減少調用次數。
- 不能調用AssetBundle.Unload(false)后再調用AssetBundle.Unload(true).
- 不再使用的GameObject直接Destroy即可。
實測
以上圖片和解釋來自於官方資料總結,但從加載接口可以看到是Unity5.3之前的版本,而在Unity5.3.4中是否真的如上面所言,我們仍需要“眼見為實”。畢竟沒有親測都是不可信的。
測試使用window平台,CPU i5-4750 3.2GHZ。從StreamingAsset下加載,測試時內存存在0.1M波動,但多次測試結果非常穩定。而加載耗時波動較大,不過不影響結論。
測試過程為:啟動空的場景,先加載AssetBundle,再調用LoadAllAssets加載全部資源。在這個過程中,通過profile查看內存。
測試包含不壓縮、LZMA壓縮、LZ4壓縮三種格式,壓縮相關在Unity5-ABSystem(一):AssetBundle原理 中有解釋。
備注:unload(false)和unload(true)在一次測試中不能同時調用,所以其實是兩次測試結果。由於表格顯示關系,不要理解成unload(false)一次內存下降,unload(true)一次內存下降。例如下表第一行,調用前是203.3M,調用unload(true)直接降到37.4M,下降165.9M,但表格上標記(-82.1),僅僅是因為不想建立兩個表格。
www加載實測
使用www方式異步加載結果如下:
| 格式 | ab 大小 |
啟動 內存 |
www加載 | LoadAllAssets | dispose() | unload(false) | unload(true) | 加載ab耗時 | 加載全部耗時 |
|---|---|---|---|---|---|---|---|---|---|
| 不 壓縮 |
81.3M | 37.4M | 121.4M(+84) | 203.3M(+81.9) | 203.3M(+0) | 119.5M(-83.8) | 37.4M(-82.1) | 99ms | 66ms |
| LZMA | 44.9M | 37.4M | 117.3M(+79.9) | 199.2M(+81.9) | 199.2M(+0) | 1195M(-79.7) | 37.4M(-82.1) | 3377ms | 119ms |
| LZ4 | 71.8M | 37.4M | 111.6(+74.2) | 193.5M(+81.9) | 193.5M(+0) | 119.4M(-74.1) | 37.4(-82) | 99.7ms | 176ms |
可以看到,使用www加載AssetBundle時,內存均有比未壓縮之前ab包稍大的上漲,此時占用1倍AssetBundle大小內存。猜測這里www將整個AssetBundle原始文件均載入了內存,從加載耗時來看,如果是LZMA,還進行了3秒多的解壓。這里不壓縮居然內存漲幅最大,不知道www暗地里做了什么神奇的事情。
接着加載所有資源,非常整齊地都增長了81.9M。這部分就是我們希望使用的資源。但此時內存占用是我們資源的兩倍左右。
測試中遇到比較奇怪的是,無論www.dispose或www = null是否調用,unload(false)均能釋放WebStream數據內存,也就是上表中unload(false)減少部分。而調用www.dispose和www = null ,內存無明顯變化。也就是並不需要釋放www對WebStream的引用或www根本未引用WebStream。
與官方大佬描述不符,感到十分惶恐,貼出原書壓壓驚。
調用AssetBundle.Unload(true)之后,內存回到啟動值。
最后,不壓縮的AssetBundle加載資源最快,成績為82M/66ms,LZMA即使已經被解壓過了,加載時間仍然接近不壓縮的兩倍,而LZ4因為加載時解壓,加載時間接近不壓縮的三倍。
LoadFromFile加載實測
使用LoadFromFile和LoadFromFileAsync接口加載,除了異步時間以外,其余差異不大,以LoadFromFile為例,測試結果如下。
| 格式 | ab 大小 |
啟動 內存 |
LoadFromFile | LoadAllAssets | unload(false) | unload(true) | 加載ab耗時 | 加載全部耗時 |
|---|---|---|---|---|---|---|---|---|
| 不 壓縮 |
81.3M | 38.5M | 38.5M(+0) | 120.5M(+82) | 120.5M(+0) | 38.5M(-82) | 1.3ms | 70.7ms |
| LZMA | 44.9M | 38.5M | 118.5M(+80) | 200.4M(+81.9) | 120.5M(-79.9) | 38.5M(-82) | 3312ms | 116ms |
| LZ4 | 71.8M | 38.5M | 38.5(+0) | 120.5M(+82) | 120.5M(+0) | 38.5(-82) | 1.2ms | 179ms |
其中LZMA內存表現與www加載結果相近,最終仍然是AssetBundle本身+所有資源兩倍內存,應該是它需要立即全部解壓導致。而不壓縮和LZ4格式,在加載AssetBundle時沒有帶來任何內存和時間上的額外開銷,只有加載全部資源時,有資源內存的占用。同時對於這兩種格式,調用unload(false)也沒有任何效果,因為本身就從未分配過這部分內存。
建議
- 無論使用何種方式壓縮AssetBundle,均使用AssetBundle.LoadFromFile和AssetBundle.LoadFromFileAsync接口加載AssetBundle。避免使用www加載。
- 避免使用LZMA壓縮,有較長的解壓時間和兩倍實際資源的內存占用,盡量使用LZ4壓縮(需要升級5.3或更高)。
- 如果使用LZ4或不壓縮包體過大,可以自己再對AssetBundle進行壓縮,首次進入游戲時解壓。

