Unity中的記憶體

2022-09-09 16:06:29 字數 4081 閱讀 9415

unity3d在記憶體占用上一直被人詬病,特別是對於面向移動裝置的遊戲開發,動輒記憶體占用飆上一兩百兆,導致記憶體資源耗盡,從而被系統強退造成極差的體驗。類似這種情況並不少見,但是絕大部分都是可以避免的。雖然理論上unity的記憶體管理系統應當為開發者分憂解難,讓大家投身到更有意義的事情中去,但是對於unity對記憶體的管理方式,官方文件中並沒有太多的說明,基本需要依靠自己摸索。最近在接手的專案中存在嚴重的記憶體問題,在參照文件和unity answer眾多猜測和證實之後,稍微總結了下unity中的記憶體的分配和管理的基本方式,在此共享。

雖然unity標榜自己的記憶體使用全都是「managed memory」,但是事實上你必須正確地使用記憶體,以保證**機制正確執行。如果沒有做應當做的事情,那麼場景和**很有可能造成很多非必要記憶體的占用,這也是很多unity開發者抱怨記憶體占用太大的原因。接下來我會介紹unity使用記憶體的種類,以及相應每個種類的優化和使用的技巧。遵循使用原則,可以讓非必要資源盡快得到釋放,從而降低記憶體占用。

實際上unity遊戲使用的記憶體一共有三種:程式**、託管堆(managed heap)以及本機堆(native heap)。

程式**包括了所有的unity引擎,使用的庫,以及你所寫的所有的遊戲**。在編譯後,得到的執行檔案將會被載入到裝置中執行,並占用一定記憶體。這部分記憶體實際上是沒有辦法去「管理」的,它們將在記憶體中從一開始到最後一直存在。乙個空的unity預設場景,什麼**都不放,在ios裝置上占用記憶體應該在17mb左右,而加上一些自己的**很容易就飆到20mb左右。想要減少這部分記憶體的使用,能做的就是減少使用的庫,稍後再說。

託管堆是被mono使用的一部分記憶體。mono專案乙個開源的.net框架的一種實現,對於unity開發,其實充當了基本類庫的角色。託管堆用來存放類的例項(比如用new生成的列表,例項中的各種宣告的變數等)。「託管」的意思是mono「應該」自動地改變堆的大小來適應你所需要的記憶體,並且定時地使用垃圾**(garbage collect)來釋放已經不需要的記憶體。關鍵在於,有時候你會忘記清除對已經不需要再使用的記憶體的引用,從而導致mono認為這塊記憶體一直有用,而無法**。

最後,本機堆是unity引擎進行申請和操作的地方,比如貼圖,音效,關卡資料等。unity使用了自己的一套記憶體管理機制來使這塊記憶體具有和託管堆類似的功能。基本理念是,如果在這個關卡裡需要某個資源,那麼在需要時就載入,之後在沒有任何引用時進行解除安裝。聽起來很美好也和託管堆一樣,但是由於unity有一套自動載入和解除安裝資源的機制,讓兩者變得差別很大。自動載入資源可以為開發者省不少事兒,但是同時也意味著開發者失去了手動管理所有載入資源的權力,這非常容易導致大量的記憶體占用(貼圖什麼的***),也是unity給人留下「吃記憶體」印象的罪魁禍首。

這部分的優化相對簡單,因為能做的事情並不多:主要就是減少打包時的引用庫,改一改build設定即可。對於乙個新專案來說不會有太大問題,但是如果是已經存在的專案,可能改變會導致原來所需要的庫的缺失(雖說一般來說這種可能性不大),因此有可能無法做到最優。

當使用unity開發時,預設的mono包含庫可以說大部分用不上,在player setting(edit->project setting->;player或者shift+ctrl(command)+b裡的player setting按鈕)面板裡,將最下方的optimization欄目中「api compatibility level」選為.net 2.0 subset,表示你只會使用到部分的.net 2.0 subset,不需要unity將全部.net的api包含進去。接下來的「stripping level」表示從build的庫中剝離的力度,每乙個剝離選項都將從打包好的庫中去掉一部分內容。你需要保證你的**沒有用到這部分被剝離的功能,選為「use micro mscorlib」的話將使用最小的庫(一般來說也沒啥問題,不行的話可以試試之前的兩個)。庫剝離可以極大地降低打包後的程式的尺寸以及程式**的記憶體占用,唯一的缺點是這個功能只支援pro版的unity。

這部分優化的力度需要根據**所用到的.net的功能來進行調整,有可能不能使用subset或者最大的剝離力度。如果超出了限度,很可能會在需要該功能時因為找不到相應的庫而crash掉(ios的話很可能在xcode編譯時就報錯了)。比較好地解決方案是仍然用最強的剝離,並輔以較小的第三方的類庫來完成所需功能。乙個最常見問題是最大剝離時sysytem.xml是不被subset和micro支援的,如果只是為了xml,完全可以匯入乙個輕量級的xml庫來解決依賴(unity官方推薦這個)。

關於每個設定對應支援的庫的詳細列表,可以在這裡找到。關於每個剝離級別到底做了什麼,unity的文件也有說明。實際上,在遊戲開發中絕大多數被剝離的功能使用不上的,因此不管如何,庫剝離的優化方法都值得一試。

unity有一篇不錯的關於託管堆**如何寫比較好的說明,在此基礎上我個人有一些補充。

首先需要明確,託管堆中儲存的是你在你的**中申請的記憶體(不論是用js,c#還是boo寫的)。一般來說,無非是new或者instantiate兩種生成object的方法(事實上instantiate中也是呼叫了new)。在接收到alloc請求後,託管堆在其上為要新生成的物件例項以及其實例變數分配記憶體,如果可用空間不足,則向系統申請更多空間。

當你使用完乙個例項物件之後,通常來說在指令碼中就不會再有對該物件的引用了(這包括將變數設定為null或其他引用,超出了變數的作用域,或者對unity物件傳送destory())。在每隔一段時間,mono的垃圾**機制將檢測記憶體,將沒有再被引用的記憶體釋放**。總的來說,你要做的就是在盡可能早的時間將不需要的引用去除掉,這樣**機制才能正確地把不需要的記憶體清理出來。但是需要注意在記憶體清理時有可能造成遊戲的短時間卡頓,這將會很影響遊戲體驗,因此如果有大量的記憶體**工作要進行的話,需要盡量選擇合適的時間。

如果不是必要,應該在遊戲進行的過程中儘量減少對gameobject的instantiate()和destroy()呼叫,因為對計算資源會有很大消耗。在便攜裝置上短時間大量生成和摧毀物體的話,很容易造成瞬時卡頓。如果記憶體沒有問題的話,盡量選擇先將他們收集起來,然後在合適的時候(比如按暫停鍵或者是關卡切換),將它們批量地銷毀並且**記憶體。mono的記憶體**會在後台自動進行,系統會選擇合適的時間進行垃圾**。在合適的時候,也可以手動地呼叫system.gc.collect()來建議系統進行一次垃圾**。要注意的是這裡的呼叫真的僅僅只是建議,可能系統會在一段時間後在進行**,也可能完全不理會這條請求,不過在大部分時間裡,這個呼叫還是靠譜的。

在關卡結束的時候,這個關卡中所使用的所有資源將會被解除安裝掉(除非被標記了dontdestroyonload)的資源。注意不僅是dontdestroyonload的資源本身,其相關的所有資源在關卡切換時都不會被解除安裝。dontdestroyonload一般被用來在關卡之間儲存一些玩家的狀態,比如分數,級別等偏向文字的資訊。如果dontdestroyonload了乙個包含很多資源(比如大量貼圖或者聲音等大記憶體占用的東西)的話,這部分資源在場景切換時無法解除安裝,將一直占用記憶體,這種情況應該盡量避免。

另外一種需要注意的情況是指令碼中對資源的引用。大部分指令碼將在場景轉換時隨之失效並被**,但是,在場景之間被保持的指令碼不在此列(通常情況是被附著在dontdestroyonload的gameobject上了)。而這些指令碼很可能含有對其他物體的component或者資源的引用,這樣相關的資源就都得不到釋放,這絕對是不想要的情況。另外,static的單例(singleton)在場景切換時也不會被摧毀,同樣地,如果這種單例含有大量的對資源的引用,也會成為大問題。因此,儘量減少**的耦合和對其他指令碼的依賴是十分有必要的。如果確實無法避免這種情況,那應當手動地對這些不再使用的引用物件呼叫destroy()或者將其設定為null。這樣在垃圾**的時候,這些記憶體將被認為已經無用而被**。

需要注意的是,unity在乙個場景開始時,根據場景構成和引用關係所自動讀取的資源,只有在讀取乙個新的場景或者reset當前場景時,才會得到清理。因此這部分記憶體占用是不可避免的。在小記憶體環境中,這部分初始記憶體的占用十分重要,因為它決定了你的關卡是否能夠被正常載入。因此在計算資源充足或是關卡開始之後還有機會進行載入時,儘量減少hierarchy中的引用,變為手動用resource.load,將大大減少記憶體占用。在resource.unloadasset()和resources.unloadunusedassets()時,只有那些真正沒有任何引用指向的資源會被**,因此請確保在資源不再使用時,將所有對該資源的引用設定為null或者destroy。同樣需要注意,這兩個unload方法僅僅對resource.load拿到的資源有效,而不能**任何場景開始時自動載入的資源。與此類似的還有assetbundle的load和unload方法,靈活使用這些手動自願載入和解除安裝的方法,是優化unity記憶體占用的不二法則~

Unity記憶體優化

一 大概標準 1.紋理 40m 2.mono 30m 3.animation 20m 4.mesh 10m 6.font 10m 7.audio 5m 8.gfxdriver 25m 9.resourcemanager 視情況而定 跟 你resourcemanager裡放了多少檔案 該標準是我做 m...

Unity記憶體理解

今天講一下unity得記憶體理解,幫助大家更深一步理解它,簡單明瞭,很好理解,也很好記住。1 unity記憶體管理,基本上是自動管理,分為兩個 堆記憶體,棧記憶體。2 棧記憶體 主要儲存小而短得資料。主要是一些值型別得資料,分配和 很簡單,快捷。3 堆記憶體 主要儲存大而時間長的資料,主要是引用型別...

unity 中Texture大小與占用記憶體關係

例子1 使用rgba 32bit真彩 truecolor 占用記憶體 4bytes 512 512 1mb 例子2 使用rgb etc 4bit壓縮,占用記憶體 0.5bytes 512 512 128kb。即8位 bit 的圖 占用記憶體 1bytes 大小 那麼 乙個1024 1024大小 32...