Unity WebGL與其它平臺有何不同?
一些用戶已經熟悉了部分內存有所限制的的平臺。而對於其它如桌面和WebPlayer平臺,到目前爲止內存還不是問題。
在內存方面,主機平臺相對其它平臺較爲簡單,因爲您可以準確的知道內存是如何使用的。這允許您可以很好的管理內存,並保證您的遊戲內容完美運行。在移動平臺,內存管理變的有些複雜,因爲設備種類繁多,但至少您可以選擇最低標準的設備,並根據市場情況忽視那些相較於該標準更爲的低端設備。
在網頁平臺,就沒有那麼輕鬆了。理想情況下,所有終端用戶都擁有64位瀏覽器和大量內存,但事實卻相距甚遠。首先,您無法通過任何方法知道,正運行您的內容的硬件規格。其次,除了用戶的操作系統和瀏覽器外,您並不知道其它信息。最後,終端用戶可能像運行其它網頁一樣運行您的WebGL內容。因此這是一個非常複雜的問題。
概覽
下圖是在瀏覽器上運行Unity WebGL內容時的內存概覽:
正如上圖所示,存在幾組內存分配:DOM,Unity堆,資源數據和代碼,這些內容都會在網頁加載時持久存在於內存中。而其它諸如 Asset Bundles, WebAudio 和 Memory FS 何時加載則取決於您的內容運行情況。(例如:Asset Bundle下載,音頻播放等等)
在加載期間, 一些瀏覽器在asm.js解析和編譯時會產生臨時內存分配,這偶爾也會導致部分使用32位瀏覽器的用戶出現內存溢出的問題。
Unity堆
通常來說,Unity堆是指包含了所有Unity特有的遊戲對象、組件、紋理、着色器 等等的內存塊。
在WebGL平臺,Unity堆的大小需要提前獲知,瀏覽器才能對此分配空間,並且內存空間一旦分配,就無法改變內存緩衝區大小。
負責Unity 堆內存分配的代碼如下:
1.buffer = new ARrayBuffer(TOTAL_MEMORY);
這段代碼可以在所生成的build.js中找到,並通過瀏覽器的JS虛擬機來執行。
TOTAL_MEMORY 是在Player Settings 中的WebGL Memory Size中設置的總內存。默認爲256MB,但這是我們隨意設定的值,事實上,一個空項目運行僅需16MB。
然而,真實世界中游戲內容可能會需要更多的內存空間,大部分情況下都需要256或者386MB。請記住,項目需要的內存越多,能夠運行它的終端用戶就越少。
源代碼/編譯代碼內存
在代碼可以被執行之前,它需要如下步驟:
- 下載
- 複製到一個文本域
-
編譯
請慎重考慮,上述的每一個步驟都將請求大量內存。因爲:
- 下載緩衝區是臨時的,但是源代碼和編譯代碼將持久存在於內存中。
-
下載緩衝區和源代碼大小,都是Unity所生成的未壓縮的js大小。按照以下步驟,您可以估算它們需要多少內存:
- 構建一個發佈版本。
- 將jsgz 、datagz重命名爲*.gz文件,並通過壓縮工具對它們進行解包。
-
解壓縮後的大小就是它們在瀏覽器內存中的大小。
-
編譯代碼的大小取決於瀏覽器。
優化內存的一個簡單方法是啓用Strip Engine Code,這樣您發佈的版本將不包含那些不必需的原生引擎代碼(例如:如果不需要2D物理模塊,它將被剝離)。請注意:託管代碼一定會被剝離。
千萬要記住,異常捕捉和第三方插件也將增加代碼大小。正如之前所說,我們已經注意到用戶需要添加空值檢查和數組邊界檢測的代碼,但不希望完整的異常檢測支持會帶來過多的內存(及性能)消耗。要實現這點,您可以通過編輯器腳本傳遞
–emit-null-checks 和 –enable-array-bounds-check 到il2cpp,例如
PlayerSettings.SetPropertyString("additionalIl2CppArgs", "--emit-null-checks --enable-array-bounds-check");
最後請記住,構建開發版本產生的代碼尺寸更大,因爲它不曾縮減。這不是問題,畢竟最終交給用戶的會是發佈版。
資源數據
在其它平臺上,一個應用可以簡單地訪問位於固定存儲空間(硬盤,閃存等等)的文件。而在網頁平臺上這是不可能的,因爲出於安全考慮,網頁平臺無法訪問真正的文件系統。因此,Unity WebGL 數據(.data文件)一旦被下載,就會永遠存儲在內存中。這樣做的缺點就是它相對其它平臺將需要更多的內存(例如5.3中.data文件以lz4壓縮的形式存儲在內存中)。例如,下圖是分析器顯示的一個項目生成了約40MB的數據文件(在256MB Unity堆的設置下):
.data 文件中包含了什麼?它是Unity所生成的文件集合,包含以下內容:data.unity3d (所有的場景,它們依賴於Resources文件夾中的資源和所有內容),unity_default_resources和少量引擎所需的小文件。
爲了知曉資源的準確總大小,您需要在發佈至WebGL平臺後查看Temp\StagingArea\Data目錄下的data.unity3d (Temp文件夾將會在Unity編輯器關閉時被刪除)。另外,您也可以通過查看UnityLoader.js 中的DataRequest差值得知素材資源的準確大小。
new DataRequest(0, 39065934, 0, 0).open('GET', '/data.unity3d');
(這段代碼根據Unity版本不同,寫法可能有些區別——示例是Unity 5.4)
內存文件系統
雖然不存在真實的文件系統,正如前文所述,您的Unity WebGL內容仍然可以讀寫文件。相對於其它平臺的主要區別在於,WebGL平臺的文件輸入/輸出操作實際上都是對內存的讀/寫操作。很重要一點是,這個內存文件系統並不存在於Unity 堆中。因此,它將需要額外的內存。例如,下面這個輸出數組到文件的示例:
var buffer = new byte [10*1014*1024];
File.WriteAllBytes(Application.temporaryCachePath + "/buffer.bytes", buffer);
這個文件將會被寫入到內存中,並且在瀏覽器的分析器也可以查看到。
請注意:Unity堆的大小爲256MB。
同樣,Unity的緩存系統依賴於文件系統,所以WebGL平臺整個緩存存儲也是在內存中進行的。這意味着像PlayerPrefs和緩存的Asset Bundles也會被持久化到內存中,而不存在於Unity堆中。
Asset Bundles
減少WebGL平臺內存消耗的最佳方法之一是使用Asset Bundles (如果您對Asset Bundles不熟悉,請查閱Unity使用手冊或通過教程學習)。然而,根據使用方式不同,它們將會對內存消耗帶來巨大影響(Unity堆中和堆外都會受此影響),這將有可能導致您的內容無法運行在32位瀏覽器上。
如果真的需要使用Asset Bundle,您會將所有資源打包到一個單獨的Asset Bundle嗎?
千萬別這麼做!即使那樣可能會減少網頁加載期間的壓力,您仍然需要下載(極可能無比巨大的)Asset Bundle,從而導致內存使用高峯。來看看下載AB前的內存使用情況。
如您所見,256MB被分配給Unity堆。下圖是沒有經過緩存的Asset Bundle下載:
現在看到的是額外的緩存,大約與硬盤中的Asset Bundle(約65mb)大小相同,它是通過XHR分配的。這只是一個臨時緩存,但它將導致連續幾幀的內存高峯,直至垃圾收集器啓動。
如何最小化內存高峯?爲每個資源創建一個Asset Bundle?想法不錯,但明顯不合實際。
事實上,對於如何做能夠減少內存高峯並沒有普遍的標準,這取決於您項目的實際需求。
最後,在資源使用完畢後記得通過AssetBundle.Unload卸載Asset Bundle。
Asset Bundle緩存
Asset Bundle緩存與其它平臺一樣,您只需要使用WWW.LoadFromCacheOrDownload。它們最大的區別就是內存消耗。在Unity WebGL中,AB緩存依賴於IndexedDB,IndexedDB是由目前內存文件系統所支持的emscripten編譯器實現。
下圖使用LoadFromCacheOrDownload下載Asset Bundle的內存使用情況:
如您所見,Unity堆使用了512MB,並額外分配了約4MB的內存。
下圖是加載Asset Bundle後的內存情況:
額外需要的內存跳到了約167mb。這是該Asset Bundle所需的額外內存(壓縮包約爲64mb)。下圖是js虛擬機垃圾收集器啓動後的內存情況:
可以看到現在有了一些改善,但仍需約85mb的內存,其中大部分內存用於將Asset Bundle緩存到內存文件系統。這些內存即使卸載了Asset Bundle也不會回收。還有一點很重要,當玩家第二次在瀏覽器中運行遊戲時,這些內存會被立即加載,甚至在加載Asset Bundle之前。
下圖是Chrome的內存截圖以供參考:
同樣,在Unity堆外還有其它緩存相關的臨時內存分配,以供Asset Bundle系統使用。壞消息是最近我們發現它比預想的更大。好消息是它將在未來的Unity 5.5 Beta 4,5.3.6 Patch 6和5.4.1 Patch 2中得以修復。
對於更早的Unity版本,萬一您的Unity WebGL內容已經上線或即將發佈,而您又不想升級項目,一個快速的變通方法是通過編輯器腳本的設置以下屬性:
PlayerSettings.SetPropertyString("emscriptenArgs", " -s MEMFS_APPEND_TO_TYPED_ARRAYS=1", BuildTargetGroup.WebGL);
最小化Asset Bundle緩存內存消耗的長遠解決方案是,使用WWW構造器替代LoadFromCacheOrDownload(),或者您使用新的UnityWebRequest API 時,調用UnityWebRequest.GetAssetBundle()不要帶有哈希或版本參數。
其次是在XMLHttpRequest層使用替代的緩存機制,繞過內存文件系統,將下載的文件直接存儲到indexedDB中。我們已經開發了這樣的工具併發布在Asset Store中。您可以免費將它用於您的項目,也可以自定義以滿足特殊需求。
Asset Bundle壓縮
Unity 5.3和5.4均支持LZMA和LZ4兩種壓縮方式。然而,即使使用LZMA(默認)壓縮相對於LZ4或未壓縮下載的包更小,但它在WebGL平臺上還是有些缺點:它會導致明顯的運行延遲,並且需要更多的內存。因此強烈建議使用LZ4或者未壓縮的格式(實際上,Unity 5.5的WebGL平臺將不再支持對Asset Bundle的LZMA壓縮),爲了彌補相比LZMA壓縮的下載尺寸過大,您可能希望使用gzip/brotli來壓縮Asset Bundle,並配置到您的服務端。
查閱Unity使用手冊以獲得更多關於Asset Bundle壓縮的信息
網頁音頻
音頻在Unity WebGL上的實現方式有所不同。這對內存意味着什麼?
Unity將會在JavaScript中創建特定的AudioBuffer的對象,以便它們可以通過WebAudio進行播放。
由於WebAudio緩存位於Unity堆外,因此無法通過Unity 分析器進行跟蹤分析,您需要使用瀏覽器專用的工具,來查看音頻使用了多少內存。示例如下(火狐瀏覽器, about:memory page):
考慮到那些Audio Buffers保存的是未解壓的數據,其可能不適用於大型音頻片段資源(例如:背景音樂)。對於那些資源,你可能希望自己編寫js插件,以便使用<audio>標籤。這種方式下音頻文件會保持壓縮,因此需要的內存更少。
FAQ
問:減少內存使用的最佳實踐是什麼?
答:概括如下:
- 減少Unity堆的大小
- 儘可能保持“WebGL Memory Size”足夠小
- 減少代碼量
- 啓用Strip Engine Code
- 禁用異常檢測
- 避免使用第三方插件
- 減少數據大小
- 使用Asset Bundles
-
使用Crunch紋理壓縮
問:是否存在能夠決定最小WebGL Memory Size的策略?
答:有,最佳策略是使用內存分析器,分析您的內容實際所需的內存大小,然後據此改變WebGL Memory Size。
以空項目爲例,內存分析器告訴我們總的使用量僅爲16mb(這個值可能在不同Unity版本上有所不同):這意味着只須設置WebGL Memory Size大於16MB即可。當然,內存的總使用量將會依據您的內容而有所不同。
然而,如果因爲某些原因無法使用分析器,可以簡單地通過不斷地減少WebGL Memory Size 值,直到發現您的內容真正所需要的最小內存使用量爲止。
另外非常值得注意的是,任何不是16的倍數的值都將被自動的四捨五入(在運行時)爲下一個16的倍數,這是Emscripten編譯器所要求的。
WebGL Memory Size(MB)設置將決定生成的html中TOTAL_MEMORY(bytes)的值。
所以,爲了在不重新構建項目的前提下,反覆測試內存堆的值,推薦使用更改html的方式。一旦您通過此方式發現適合的值,只需在Unity項目設置中更改WebGL Memory Size即可。
最後,記住Unity的分析器將佔用一些來自Unity堆的內存,所以在使用分析器時可能需要增加WebGL內存大小。
問:運行時發生內存溢出,如何修復?
答:這取決於是Unity,還是瀏覽器的內存溢出。這個錯誤信息將會指出問題所在以及解決辦法:“如果您是該內容開發者,請在WebGL設置中爲您的應用分配更多(或更少)的內存。”此時您可以據此調整WebGL內存大小設置。然而還有很多可以解決內存溢出的方法。如果出現以下錯誤信息:
除了消息內容,您還可以嘗試減少代碼和數據的大小。這是因爲當瀏覽器加載網頁時,它將試圖爲一些內容尋找空餘的內存,其中最重要的是:代碼,數據,Unity堆和被編譯的asm.js。它們可能相當大,尤其是數據和Unity堆內存,這對32位瀏覽器來說可能是問題。
在一些例子中,儘管存在足夠多的空餘內存,瀏覽器仍將加載失敗,因爲內存是碎片化的。這就是爲什麼有時候您的內容可能在重啓瀏覽器之後,可以成功加載的原因。
另一種情況是,當Unity 內存溢出時提示以下信息:
在這種情況下,您需要優化您的Unity項目。
問:如何衡量內存消耗?
答:爲了分析內容所使用的瀏覽器內存,可以使用火狐瀏覽器的內存工具或Chrome堆快照。但它們不會顯示WebAudio內存使用情況,因此還可以獲取火狐瀏覽器的about:memory頁面快照,然後通過搜索“webaudio”找到。如果您需要通過JavaScript分析內存,請嘗試使用window.performance.memory(只支持Chrome)。
使用Unity分析器測量Unity堆內存使用。但請注意,您可能需要增加WebGL的內存大小,以便能夠使用分析器。
此外,我們一直在致力於開發一個新的工具,以便您能分析發佈版本:構建WebGL版本,然後訪問http://files.unity3d.com/build-report/即可使用該工具。雖然這在Unity5.4下已經可用,但請您注意,這還是正在開發中的功能,並且隨時會更改或被刪除。但至少現在可以使用它達到測試的目的。
問:WebGL Memory Size的最小值與最大值是多少?
答:16MB是最小的,最大是2032MB,然而我們通常建議保持在512MB以下。
是否可能出於開發目的而需要分配超過2032MB的內存?
這是一個技術上的限制:2048MB(或更多)將會超出TypeArray所用的32位有符號整型的最大值,而TypeArray被用於在JavaScript中實現Unity堆。
問:爲何Unity 堆大小不可改變?
答:我們一直在考慮使用Emscripten編譯器標誌ALLOW_MEMO