小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

Unity 3D中的內(nèi)存管理

 kiki的號(hào) 2017-06-20

本文歡迎轉(zhuǎn)載,但煩請(qǐng)保留此行出處信息:http://www./2012/11/memory-in-unity3d/

Unity3D在內(nèi)存占用上一直被人詬病,特別是對(duì)于面向移動(dòng)設(shè)備的游戲開(kāi)發(fā),動(dòng)輒內(nèi)存占用飆上一兩百兆,導(dǎo)致內(nèi)存資源耗盡,從而被系統(tǒng)強(qiáng)退造成極差的體驗(yàn)。類似這種情況并不少見(jiàn),但是絕大部分都是可以避免的。雖然理論上Unity的內(nèi)存管理系統(tǒng)應(yīng)當(dāng)為開(kāi)發(fā)者分憂解難,讓大家投身到更有意義的事情中去,但是對(duì)于Unity對(duì)內(nèi)存的管理方式,官方文檔中并沒(méi)有太多的說(shuō)明,基本需要依靠自己摸索。最近在接手的項(xiàng)目中存在嚴(yán)重的內(nèi)存問(wèn)題,在參照文檔和Unity Answer眾多猜測(cè)和證實(shí)之后,稍微總結(jié)了下Unity中的內(nèi)存的分配和管理的基本方式,在此共享。

雖然Unity標(biāo)榜自己的內(nèi)存使用全都是“Managed Memory”,但是事實(shí)上你必須正確地使用內(nèi)存,以保證回收機(jī)制正確運(yùn)行。如果沒(méi)有做應(yīng)當(dāng)做的事情,那么場(chǎng)景和代碼很有可能造成很多非必要內(nèi)存的占用,這也是很多Unity開(kāi)發(fā)者抱怨內(nèi)存占用太大的原因。接下來(lái)我會(huì)介紹Unity使用內(nèi)存的種類,以及相應(yīng)每個(gè)種類的優(yōu)化和使用的技巧。遵循使用原則,可以讓非必要資源盡快得到釋放,從而降低內(nèi)存占用。


Unity中的內(nèi)存種類

實(shí)際上Unity游戲使用的內(nèi)存一共有三種:程序代碼、托管堆(Managed Heap)以及本機(jī)堆(Native Heap)。

程序代碼包括了所有的Unity引擎,使用的庫(kù),以及你所寫(xiě)的所有的游戲代碼。在編譯后,得到的運(yùn)行文件將會(huì)被加載到設(shè)備中執(zhí)行,并占用一定內(nèi)存。這部分內(nèi)存實(shí)際上是沒(méi)有辦法去“管理”的,它們將在內(nèi)存中從一開(kāi)始到最后一直存在。一個(gè)空的Unity默認(rèn)場(chǎng)景,什么代碼都不放,在iOS設(shè)備上占用內(nèi)存應(yīng)該在17MB左右,而加上一些自己的代碼很容易就飆到20MB左右。想要減少這部分內(nèi)存的使用,能做的就是減少使用的庫(kù),稍后再說(shuō)。

托管堆是被Mono使用的一部分內(nèi)存。Mono項(xiàng)目一個(gè)開(kāi)源的.net框架的一種實(shí)現(xiàn),對(duì)于Unity開(kāi)發(fā),其實(shí)充當(dāng)了基本類庫(kù)的角色。托管堆用來(lái)存放類的實(shí)例(比如用new生成的列表,實(shí)例中的各種聲明的變量等)?!巴泄堋钡囊馑际荕ono“應(yīng)該”自動(dòng)地改變堆的大小來(lái)適應(yīng)你所需要的內(nèi)存,并且定時(shí)地使用垃圾回收(Garbage Collect)來(lái)釋放已經(jīng)不需要的內(nèi)存。關(guān)鍵在于,有時(shí)候你會(huì)忘記清除對(duì)已經(jīng)不需要再使用的內(nèi)存的引用,從而導(dǎo)致Mono認(rèn)為這塊內(nèi)存一直有用,而無(wú)法回收。

最后,本機(jī)堆是Unity引擎進(jìn)行申請(qǐng)和操作的地方,比如貼圖,音效,關(guān)卡數(shù)據(jù)等。Unity使用了自己的一套內(nèi)存管理機(jī)制來(lái)使這塊內(nèi)存具有和托管堆類似的功能?;纠砟钍牵绻谶@個(gè)關(guān)卡里需要某個(gè)資源,那么在需要時(shí)就加載,之后在沒(méi)有任何引用時(shí)進(jìn)行卸載。聽(tīng)起來(lái)很美好也和托管堆一樣,但是由于Unity有一套自動(dòng)加載和卸載資源的機(jī)制,讓兩者變得差別很大。自動(dòng)加載資源可以為開(kāi)發(fā)者省不少事兒,但是同時(shí)也意味著開(kāi)發(fā)者失去了手動(dòng)管理所有加載資源的權(quán)力,這非常容易導(dǎo)致大量的內(nèi)存占用(貼圖什么的你懂的),也是Unity給人留下“吃內(nèi)存”印象的罪魁禍?zhǔn)住?/p>


優(yōu)化程序代碼的內(nèi)存占用

這部分的優(yōu)化相對(duì)簡(jiǎn)單,因?yàn)槟茏龅氖虑椴⒉欢啵褐饕褪菧p少打包時(shí)的引用庫(kù),改一改build設(shè)置即可。對(duì)于一個(gè)新項(xiàng)目來(lái)說(shuō)不會(huì)有太大問(wèn)題,但是如果是已經(jīng)存在的項(xiàng)目,可能改變會(huì)導(dǎo)致原來(lái)所需要的庫(kù)的缺失(雖說(shuō)一般來(lái)說(shuō)這種可能性不大),因此有可能無(wú)法做到最優(yōu)。

當(dāng)使用Unity開(kāi)發(fā)時(shí),默認(rèn)的Mono包含庫(kù)可以說(shuō)大部分用不上,在Player Setting(Edit->Project Setting->;Player或者Shift+Ctrl(Command)+B里的Player Setting按鈕)面板里,將最下方的Optimization欄目中“Api Compatibility Level”選為.NET 2.0 Subset,表示你只會(huì)使用到部分的.NET 2.0 Subset,不需要Unity將全部.NET的Api包含進(jìn)去。接下來(lái)的“Stripping Level”表示從build的庫(kù)中剝離的力度,每一個(gè)剝離選項(xiàng)都將從打包好的庫(kù)中去掉一部分內(nèi)容。你需要保證你的代碼沒(méi)有用到這部分被剝離的功能,選為“Use micro mscorlib”的話將使用最小的庫(kù)(一般來(lái)說(shuō)也沒(méi)啥問(wèn)題,不行的話可以試試之前的兩個(gè))。庫(kù)剝離可以極大地降低打包后的程序的尺寸以及程序代碼的內(nèi)存占用,唯一的缺點(diǎn)是這個(gè)功能只支持Pro版的Unity。

這部分優(yōu)化的力度需要根據(jù)代碼所用到的.NET的功能來(lái)進(jìn)行調(diào)整,有可能不能使用Subset或者最大的剝離力度。如果超出了限度,很可能會(huì)在需要該功能時(shí)因?yàn)檎也坏较鄳?yīng)的庫(kù)而crash掉(iOS的話很可能在Xcode編譯時(shí)就報(bào)錯(cuò)了)。比較好地解決方案是仍然用最強(qiáng)的剝離,并輔以較小的第三方的類庫(kù)來(lái)完成所需功能。一個(gè)最常見(jiàn)問(wèn)題是最大剝離時(shí)Sysytem.Xml是不被Subset和micro支持的,如果只是為了xml,完全可以導(dǎo)入一個(gè)輕量級(jí)的xml庫(kù)來(lái)解決依賴(Unity官方推薦這個(gè))。

關(guān)于每個(gè)設(shè)定對(duì)應(yīng)支持的庫(kù)的詳細(xì)列表,可以在這里找到。關(guān)于每個(gè)剝離級(jí)別到底做了什么,Unity的文檔也有說(shuō)明。實(shí)際上,在游戲開(kāi)發(fā)中絕大多數(shù)被剝離的功能使用不上的,因此不管如何,庫(kù)剝離的優(yōu)化方法都值得一試。


托管堆優(yōu)化

Unity有一篇不錯(cuò)的關(guān)于托管堆代碼如何寫(xiě)比較好的說(shuō)明,在此基礎(chǔ)上我個(gè)人有一些補(bǔ)充。

首先需要明確,托管堆中存儲(chǔ)的是你在你的代碼中申請(qǐng)的內(nèi)存(不論是用js,C#還是Boo寫(xiě)的)。一般來(lái)說(shuō),無(wú)非是new或者Instantiate兩種生成object的方法(事實(shí)上Instantiate中也是調(diào)用了new)。在接收到alloc請(qǐng)求后,托管堆在其上為要新生成的對(duì)象實(shí)例以及其實(shí)例變量分配內(nèi)存,如果可用空間不足,則向系統(tǒng)申請(qǐng)更多空間。

當(dāng)你使用完一個(gè)實(shí)例對(duì)象之后,通常來(lái)說(shuō)在腳本中就不會(huì)再有對(duì)該對(duì)象的引用了(這包括將變量設(shè)置為null或其他引用,超出了變量的作用域,或者對(duì)Unity對(duì)象發(fā)送Destory())。在每隔一段時(shí)間,Mono的垃圾回收機(jī)制將檢測(cè)內(nèi)存,將沒(méi)有再被引用的內(nèi)存釋放回收??偟膩?lái)說(shuō),你要做的就是在盡可能早的時(shí)間將不需要的引用去除掉,這樣回收機(jī)制才能正確地把不需要的內(nèi)存清理出來(lái)。但是需要注意在內(nèi)存清理時(shí)有可能造成游戲的短時(shí)間卡頓,這將會(huì)很影響游戲體驗(yàn),因此如果有大量的內(nèi)存回收工作要進(jìn)行的話,需要盡量選擇合適的時(shí)間。

如果在你的游戲里,有特別多的類似實(shí)例,并需要對(duì)它們經(jīng)常發(fā)送Destroy()的話,游戲性能上會(huì)相當(dāng)難看。比如小熊推金幣中的金幣實(shí)例,按理說(shuō)每枚金幣落下臺(tái)子后都需要對(duì)其Destory(),然后新的金幣進(jìn)入臺(tái)子時(shí)又需要Instantiate,這對(duì)性能是極大的浪費(fèi)。一種通常的做法是在不需要時(shí),不摧毀這個(gè)GameObject,而只是隱藏它,并將其放入一個(gè)重用數(shù)組中。之后需要時(shí),再?gòu)闹赜脭?shù)組中找到可用的實(shí)例并顯示。這將極大地改善游戲的性能,相應(yīng)的代價(jià)是消耗部分內(nèi)存,一般來(lái)說(shuō)這是可以接受的。關(guān)于對(duì)象重用,可以參考Unity關(guān)于內(nèi)存方面的文檔中Reusable Object Pools部分,或者Prime31有一個(gè)是用Linq來(lái)建立重用池的視頻教程(Youtube,需要翻墻,上半部分下半部分)。

如果不是必要,應(yīng)該在游戲進(jìn)行的過(guò)程中盡量減少對(duì)GameObject的Instantiate()和Destroy()調(diào)用,因?yàn)閷?duì)計(jì)算資源會(huì)有很大消耗。在便攜設(shè)備上短時(shí)間大量生成和摧毀物體的話,很容易造成瞬時(shí)卡頓。如果內(nèi)存沒(méi)有問(wèn)題的話,盡量選擇先將他們收集起來(lái),然后在合適的時(shí)候(比如按暫停鍵或者是關(guān)卡切換),將它們批量地銷(xiāo)毀并且回收內(nèi)存。Mono的內(nèi)存回收會(huì)在后臺(tái)自動(dòng)進(jìn)行,系統(tǒng)會(huì)選擇合適的時(shí)間進(jìn)行垃圾回收。在合適的時(shí)候,也可以手動(dòng)地調(diào)用System.GC.Collect()來(lái)建議系統(tǒng)進(jìn)行一次垃圾回收。要注意的是這里的調(diào)用真的僅僅只是建議,可能系統(tǒng)會(huì)在一段時(shí)間后在進(jìn)行回收,也可能完全不理會(huì)這條請(qǐng)求,不過(guò)在大部分時(shí)間里,這個(gè)調(diào)用還是靠譜的。


本機(jī)堆的優(yōu)化

當(dāng)你加載完成一個(gè)Unity的scene的時(shí)候,scene中的所有用到的asset(包括Hierarchy中所有GameObject上以及腳本中賦值了的的材質(zhì),貼圖,動(dòng)畫(huà),聲音等素材),都會(huì)被自動(dòng)加載(這正是Unity的智能之處)。也就是說(shuō),當(dāng)關(guān)卡呈現(xiàn)在用戶面前的時(shí)候,所有Unity編輯器能認(rèn)識(shí)的本關(guān)卡的資源都已經(jīng)被預(yù)先加入內(nèi)存了,這樣在本關(guān)卡中,用戶將有良好的體驗(yàn),不論是更換貼圖,聲音,還是播放動(dòng)畫(huà)時(shí),都不會(huì)有額外的加載,這樣的代價(jià)是內(nèi)存占用將變多。Unity最初的設(shè)計(jì)目的還是面向臺(tái)式機(jī),幾乎無(wú)限的內(nèi)存和虛擬內(nèi)存使得這樣的占用似乎不是問(wèn)題,但是這樣的內(nèi)存策略在之后移動(dòng)平臺(tái)的興起和大量移動(dòng)設(shè)備游戲的制作中出現(xiàn)了弊端,因?yàn)橐苿?dòng)設(shè)備能使用的資源始終非常有限。因此在面向移動(dòng)設(shè)備游戲的制作時(shí),盡量減少在Hierarchy對(duì)資源的直接引用,而是使用Resource.Load的方法,在需要的時(shí)候從硬盤(pán)中讀取資源,在使用后用Resource.UnloadAsset()和Resources.UnloadUnusedAssets()盡快將其卸載掉??傊?,這里是一個(gè)處理時(shí)間和占用內(nèi)存空間的trade off,如何達(dá)到最好的效果沒(méi)有標(biāo)準(zhǔn)答案,需要自己權(quán)衡。

在關(guān)卡結(jié)束的時(shí)候,這個(gè)關(guān)卡中所使用的所有資源將會(huì)被卸載掉(除非被標(biāo)記了DontDestroyOnLoad)的資源。注意不僅是DontDestroyOnLoad的資源本身,其相關(guān)的所有資源在關(guān)卡切換時(shí)都不會(huì)被卸載。DontDestroyOnLoad一般被用來(lái)在關(guān)卡之間保存一些玩家的狀態(tài),比如分?jǐn)?shù),級(jí)別等偏向文本的信息。如果DontDestroyOnLoad了一個(gè)包含很多資源(比如大量貼圖或者聲音等大內(nèi)存占用的東西)的話,這部分資源在場(chǎng)景切換時(shí)無(wú)法卸載,將一直占用內(nèi)存,這種情況應(yīng)該盡量避免。

另外一種需要注意的情況是腳本中對(duì)資源的引用。大部分腳本將在場(chǎng)景轉(zhuǎn)換時(shí)隨之失效并被回收,但是,在場(chǎng)景之間被保持的腳本不在此列(通常情況是被附著在DontDestroyOnLoad的GameObject上了)。而這些腳本很可能含有對(duì)其他物體的Component或者資源的引用,這樣相關(guān)的資源就都得不到釋放,這絕對(duì)是不想要的情況。另外,static的單例(singleton)在場(chǎng)景切換時(shí)也不會(huì)被摧毀,同樣地,如果這種單例含有大量的對(duì)資源的引用,也會(huì)成為大問(wèn)題。因此,盡量減少代碼的耦合和對(duì)其他腳本的依賴是十分有必要的。如果確實(shí)無(wú)法避免這種情況,那應(yīng)當(dāng)手動(dòng)地對(duì)這些不再使用的引用對(duì)象調(diào)用Destroy()或者將其設(shè)置為null。這樣在垃圾回收的時(shí)候,這些內(nèi)存將被認(rèn)為已經(jīng)無(wú)用而被回收。

需要注意的是,Unity在一個(gè)場(chǎng)景開(kāi)始時(shí),根據(jù)場(chǎng)景構(gòu)成和引用關(guān)系所自動(dòng)讀取的資源,只有在讀取一個(gè)新的場(chǎng)景或者reset當(dāng)前場(chǎng)景時(shí),才會(huì)得到清理。因此這部分內(nèi)存占用是不可避免的。在小內(nèi)存環(huán)境中,這部分初始內(nèi)存的占用十分重要,因?yàn)樗鼪Q定了你的關(guān)卡是否能夠被正常加載。因此在計(jì)算資源充足或是關(guān)卡開(kāi)始之后還有機(jī)會(huì)進(jìn)行加載時(shí),盡量減少Hierarchy中的引用,變?yōu)槭謩?dòng)用Resource.Load,將大大減少內(nèi)存占用。在Resource.UnloadAsset()和Resources.UnloadUnusedAssets()時(shí),只有那些真正沒(méi)有任何引用指向的資源會(huì)被回收,因此請(qǐng)確保在資源不再使用時(shí),將所有對(duì)該資源的引用設(shè)置為null或者Destroy。同樣需要注意,這兩個(gè)Unload方法僅僅對(duì)Resource.Load拿到的資源有效,而不能回收任何場(chǎng)景開(kāi)始時(shí)自動(dòng)加載的資源。與此類似的還有AssetBundle的Load和Unload方法,靈活使用這些手動(dòng)自愿加載和卸載的方法,是優(yōu)化Unity內(nèi)存占用的不二法則~

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多