深刻淺出聊優化:從Draw Calls到GC

0x00 前言

剛開始寫這篇文章的時候選了一個很土的題目。。。《Unity3D優化全解析》。由於這是一篇臨時起意才寫的文章,並且陳述的都是既有的事實,於是給本身「文(dou)學(bi)」加工留下的餘地就少了不少。但又以爲這塊是不得不提的一個地方,平時見到不少人對此處也給予了忽略了事,須要時纔去網上扒一些隻言片語的資料。也恰逢年前,尋思着週末認真寫點東西遇到節假日沒準也沒什麼人讀,因此索性就寫了這篇臨時的文章。題目很土,由於用了指向性很明確的「Unity3D」,讓人少了遐(瞎)想的空間,同時用了「高大全」這樣的構詞法,也讓匹夫有成爲衆矢之的的可能。。。因此最後仍是改爲了如今各位看到的題目。話很少說,下面就開始正文~正所謂「草蛇灰線,伏脈千里」。那我們首先~~web

0x01 看看優化須要從哪裏着手?

匹夫印象裏遇到的童靴,提Unity3D項目優化則必提DrawCall,這天然沒錯,但也有很很差影響。由於這會給人一個錯誤的認識:所謂的優化就是把DrawCall弄的比較低就對了。編程

對優化有這種第一印象的人不在少數,drawcall的確是一個很重要的指標,但絕非所有。爲了讓各位和匹夫能達成儘量多的共識,匹夫首先介紹一下本文可能會涉及到的幾個概念,以後會提出優化所涉及的三大方面:數組

  • drawcall是啥?其實就是對底層圖形程序(好比:OpenGL ES)接口的調用,以在屏幕上畫出東西。因此,是誰去調用這些接口呢?CPU。緩存

  • fragment是啥?常常有人說vf啥的,vertex咱們都知道是頂點,那fragment是啥呢?說它以前須要先說一下像素,像素各位應該都知道吧?像素是構成數碼影像的基本單元呀。那fragment呢?是有可能成爲像素的東西。啥叫有可能?就是最終會不會被畫出來不必定,是潛在的像素。這會涉及到誰呢?GPU。性能優化

  • batching是啥?都知道批處理是幹嗎的吧?沒錯,將批處理以前須要不少次調用(drawcall)的物體合併,以後只須要調用一次底層圖形程序的接口就行。聽上去這簡直就是優化的終極方案啊!可是,理想是美好的,世界是殘酷的,一些不足以後咱們再細聊。ide

  • 內存的分配:記住,除了Unity3D本身的內存損耗。咱們但是還帶着Mono呢啊,還有託管的那一套東西呢。更別說你一激動,又引入了本身的幾個dll。這些都是內存開銷上須要考慮到的。函數

好啦,文中的幾個概念提早講清楚了,其實各位也能看的出來匹夫接下來要說的匹夫關注的優化時須要注意的方面:工具

  • CPU方面性能

  • GPU方面優化

  • 內存方面

因此,這篇文章也會按照CPU---->GPU---->內存的順序進行。

0x02 CPU的方面的優化

上文中說了,drawcall影響的是CPU的效率,並且也是最知名的一個優化點。可是除了drawcall以外,還有哪些因素也會影響到CPU的效率呢?讓咱們一一列出暫時能想獲得的:

  • DrawCalls

  • 物理組件(Physics)

  • GC(什麼?GC不是處理內存問題的嘛?匹夫你不要騙我啊!不過,匹夫也要提醒一句,GC是用來處理內存的,可是是誰使用GC去處理內存的呢?)

  • 固然,還有代碼質量

DrawCalls:

前面說過了,DrawCall是CPU調用底層圖形接口。好比有上千個物體,每個的渲染都須要去調用一次底層接口,而每一次的調用CPU都須要作不少工做,那麼CPU必然不堪重負。可是對於GPU來講,圖形處理的工做量是同樣的。因此對DrawCall的優化,主要就是爲了儘可能解放CPU在調用圖形接口上的開銷。因此針對drawcall咱們主要的思路就是每一個物體儘可能減小渲染次數,多個物體最好一塊兒渲染。因此,按照這個思路就有了如下幾個方案:

  • 使用Draw Call Batching,也就是描繪調用批處理。Unity在運行時能夠將一些物體進行合併,從而用一個描繪調用來渲染他們。具體下面會介紹。

  • 經過把紋理打包成圖集來儘可能減小材質的使用。

  • 儘可能少的使用反光啦,陰影啦之類的,由於那會使物體屢次渲染。

Draw Call Batching

首先咱們要先理解爲什麼2個沒有使用相同材質的物體即便使用批處理,也沒法實現Draw Call數量的降低和性能上的提高。

由於被「批處理」的2個物體的網格模型須要使用相同材質的目的,在於其紋理是相同的,這樣才能夠實現同時渲染的目的。於是保證材質相同,是爲了保證被渲染的紋理相同。

所以,爲了將2個紋理不一樣的材質合二爲一,咱們就須要進行上面列出的第二步,將紋理打包成圖集。具體到合二爲一這種狀況,就是將2個紋理合成一個紋理。這樣咱們就能夠只用一個材質來代替以前的2個材質了。

而Draw Call Batching自己,也還會細分爲2種。

Static Batching 靜態批處理

看名字,猜使用的情景。

靜態?那就是不動的咯。還有呢?額,聽上去狀態也不會改變,沒有「生命」,好比山山石石,樓房校舍啥的。那和什麼比較相似呢?嗯,聰明的各位必定以爲和場景的屬性很像吧!因此咱們的場景彷佛就能夠採用這種方式來減小draw call了。

那麼寫個定義:只要這些物體不移動,而且擁有相同的材質,靜態批處理就容許引擎對任意大小的幾何物體進行批處理操做來下降描繪調用。

那要如何使用靜態批來減小Draw Call呢?你只須要明確指出哪些物體是靜止的,而且在遊戲中永遠不會移動、旋轉和縮放。想完成這一步,你只須要在檢測器(Inspector)中將Static複選框打勾便可,以下圖所示:

至於效果如何呢?

舉個例子:新建4個物體,分別是Cube,Sphere, Capsule, Cylinder,它們有不一樣的網格模型,可是也有相同的材質(Default-Diffuse)。

首先,咱們不指定它們是static的。Draw Call的次數是4次,如圖:

咱們如今將它們4個物體都設爲static,在來運行一下:

如圖,Draw Call的次數變成了1,而Saved by batching的次數變成了3。

靜態批處理的好處不少,其中之一就是與下面要說的動態批處理相比,約束要少不少。因此通常推薦的是draw call的靜態批處理來減小draw call的次數。那麼接下來,咱們就繼續聊聊draw call的動態批處理。

Dynamic Batching 動態批處理

有陰就有陽,有靜就有動,因此聊完了靜態批處理,確定跟着就要說說動態批處理了。首先要明確一點,Unity3D的draw call動態批處理機制是引擎自動進行的,無需像靜態批處理那樣手動設置static。咱們舉一個動態實例化prefab的例子,若是動態物體共享相同的材質,則引擎會自動對draw call優化,也就是使用批處理。首先,咱們將一個cube作成prefab,而後再實例化500次,看看draw call的數量。

for(int i = 0; i < 500; i++)
{
    GameObject cube;
    cube = GameObject.Instantiate(prefab) as GameObject;
}

draw call的數量:

能夠看到draw call的數量爲1,而 saved by batching的數量是499。而這個過程當中,咱們除了實例化建立物體以外什麼都沒作。不錯,unity3d引擎爲咱們自動處理了這種狀況。

可是有不少童靴也遇到這種狀況,就是我也是從prefab實例化建立的物體,爲什麼個人draw call依然很高呢?這就是匹夫上文說的,draw call的動態批處理存在着不少約束。下面匹夫就演示一下,針對cube這樣一個簡單的物體的建立,若是稍有不慎就會形成draw call飛漲的狀況吧。

咱們一樣是建立500個物體,不一樣的是其中的100個物體,每一個物體的大小都不一樣,也就是Scale不一樣。

for(int i = 0; i < 500; i++)
{
    GameObject cube;
    cube = GameObject.Instantiate(prefab) as GameObject;
    if(i / 100 == 0)
    {
        cube.transform.localScale = new Vector3(2 + i, 2 + i, 2 + i);
    }
}

draw call的數量:

咱們看到draw call的數量上升到了101次,而saved by batching的數量也降低到了399。各位看官能夠看到,僅僅是一個簡單的cube的建立,若是scale不一樣,居然也不會去作批處理優化。這僅僅是動態批處理機制的一種約束,那咱們總結一下動態批處理的約束,各位也許也能從中找到爲什麼動態批處理在本身的項目中不起做用的緣由:

    • 批處理動態物體須要在每一個頂點上進行必定的開銷,因此動態批處理僅支持小於900頂點的網格物體。

    • 若是你的着色器使用頂點位置,法線和UV值三種屬性,那麼你只能批處理300頂點如下的物體;若是你的着色器須要使用頂點位置,法線,UV0,UV1和切向量,那你只能批處理180頂點如下的物體。

    • 不要使用縮放。分別擁有縮放大小(1,1,1) 和(2,2,2)的兩個物體將不會進行批處理。
      統一縮放的物體不會與非統一縮放的物體進行批處理。

    • 使用縮放尺度(1,1,1) 和 (1,2,1)的兩個物體將不會進行批處理,可是使用縮放尺度(1,2,1) 和(1,3,1)的兩個物體將能夠進行批處理。

    • 使用不一樣材質的實例化物體(instance)將會致使批處理失敗。

    • 擁有lightmap的物體含有額外(隱藏)的材質屬性,好比:lightmap的偏移和縮放係數等。因此,擁有lightmap的物體將不會進行批處理(除非他們指向lightmap的同一部分)。

    • 多通道的shader會妨礙批處理操做。好比,幾乎unity中全部的着色器在前向渲染中都支持多個光源,併爲它們有效地開闢多個通道。

    • 預設體的實例會自動地使用相同的網格模型和材質。

    因此,儘可能使用靜態的批處理。

    物理組件

    曾幾什麼時候,匹夫在作一個策略類遊戲的時候須要在單元格上排兵佈陣,而要偵測到哪一個兵站在哪一個格子匹夫選擇使用了射線,因爲士兵單位不少,並且爲了精確每一幀都會執行檢測,那時候CPU的負擔叫一個慘不忍睹。後來匹夫果斷放棄了這種作法,而且對物理組件產生了心理的陰影。

    這裏匹夫只提2點匹夫感受比較重要的優化措施:

    1. 設置一個合適的Fixed Timestep。設置的位置如圖:

    那何謂「合適」呢?首先咱們要搞明白Fixed Timestep和物理組件的關係。物理組件,或者說遊戲中模擬各類物理效果的組件,最重要的是什麼呢?計算啊。對,須要經過計算才能將真實的物理效果展示在虛擬的遊戲中。那麼Fixed Timestep這貨就是和物理計算有關的啦。因此,若計算的頻率過高,天然會影響到CPU的開銷。同時,若計算頻率達不到遊戲設計時的要求,有會影響到功能的實現,因此如何抉擇須要各位具體分析,選擇一個合適的值。

    1. 就是不要使用網格碰撞器(mesh collider):爲啥?由於實在是太複雜了。網格碰撞器利用一個網格資源並在其上構建碰撞器。對於複雜網狀模型上的碰撞檢測,它要比應用原型碰撞器精確的多。標記爲凸起的(Convex )的網格碰撞器纔可以和其餘網格碰撞器發生碰撞。各位上網搜一下mesh collider的圖片,天然就會明白了。咱們的手機遊戲天然無需這種性價比不高的東西。

    固然,從性能優化的角度考慮,物理組件能少用仍是少用爲好。

    處理內存,卻讓CPU受傷的GC

    在CPU的部分聊GC,感受是否是怪怪的?其實小匹夫不這麼以爲,雖然GC是用來處理內存的,但的確增長的是CPU的開銷。所以它的確能達到釋放內存的效果,但代價更加沉重,會加劇CPU的負擔,所以對於GC的優化目標就是儘可能少的觸發GC。

    首先咱們要明確所謂的GC是Mono運行時的機制,而非Unity3D遊戲引擎的機制,因此GC也主要是針對Mono的對象來講的,而它管理的也是Mono的託管堆。 搞清楚這一點,你也就明白了GC不是用來處理引擎的assets(紋理啦,音效啦等等)的內存釋放的,由於U3D引擎也有本身的內存堆而不是和Mono一塊兒使用所謂的託管堆。

    其次咱們要搞清楚什麼東西會被分配到託管堆上?不錯咯,就是引用類型咯。好比類的實例,字符串,數組等等。而做爲int,float,包括結構體struct其實都是值類型,它們會被分配在堆棧上而非堆上。因此咱們關注的對象無外乎就是類實例,字符串,數組這些了。

    那麼GC何時會觸發呢?兩種狀況:

    • 首先固然是咱們的堆的內存不足時,會自動調用GC。

    • 其次呢,做爲編程人員,咱們本身也能夠手動的調用GC。

    因此爲了達到優化CPU的目的,咱們就不能頻繁的觸發GC。而上文也說了GC處理的是託管堆,而不是Unity3D引擎的那些資源,因此GC的優化說白了也就是代碼的優化。那麼匹夫以爲有如下幾點是須要注意的:

    • 字符串鏈接的處理。由於將兩個字符串鏈接的過程,實際上是生成一個新的字符串的過程。而以前的舊的字符串天然而然就成爲了垃圾。而做爲引用類型的字符串,其空間是在堆上分配的,被棄置的舊的字符串的空間會被GC當作垃圾回收。

    • 儘可能不要使用foreach,而是使用for。foreach其實會涉及到迭代器的使用,而據傳說每一次循環所產生的迭代器會帶來24 Bytes的垃圾。那麼循環10次就是240Bytes。

    • 不要直接訪問gameobject的tag屬性。好比if (go.tag == 「human」)最好換成if (go.CompareTag (「human」))。由於訪問物體的tag屬性會在堆上額外的分配空間。若是在循環中這麼處理,留下的垃圾就可想而知了。

    • 使用「池」,以實現空間的重複利用。

    • 最好不用LINQ的命令,由於它們會分配臨時的空間,一樣也是GC收集的目標。並且我很討厭LINQ的一點就是它有可能在某些狀況下沒法很好的進行AOT編譯。好比「OrderBy」會生成內部的泛型類「OrderedEnumerable」。這在AOT編譯時是沒法進行的,由於它只是在OrderBy的方法中才使用。因此若是你使用了OrderBy,那麼在IOS平臺上也許會報錯。

    代碼?腳本?

    聊到代碼這個話題,也許有人會以爲匹夫畫蛇添足。由於代碼質量因人而異,很難像上面提到的幾點,有一個明確的評判標準。也是,公寫公有理,婆寫婆有理。可是匹夫這裏要提到的所謂代碼質量是基於一個前提的:Unity3D是用C++寫的,而咱們的代碼是用C#做爲腳原本寫的,那麼問題就來了~腳本和底層的交互開銷是否須要考慮呢?也就是說,咱們用Unity3D寫遊戲的「遊戲腳本語言」,也就是C#是由mono運行時託管的。而功能是底層引擎的C++實現的,「遊戲腳本」中的功能實現都離不開對底層代碼的調用。那麼這部分的開銷,咱們應該如何優化呢?

    • 以物體的Transform組件爲例,咱們應該只訪問一次,以後就將它的引用保留,而非每次使用都去訪問。這裏有人作過一個小實驗,就是對比經過方法GetComponent<Transform>()獲取Transform組件, 經過MonoBehavor的transform屬性去取,以及保留引用以後再去訪問所須要的時間:

      • GetComponent = 619ms

      • Monobehaviour = 60ms

      • CachedMB = 8ms

      • Manual Cache = 3ms

    • 如上所述,最好不要頻繁使用GetComponent,尤爲是在循環中。

    • 善於使用OnBecameVisible()和OnBecameVisible(),來控制物體的update()函數的執行以減小開銷。

    • 使用內建的數組,好比用Vector3.zero而不是new Vector(0, 0, 0);

    • 對於方法的參數的優化:善於使用ref關鍵字。值類型的參數,是經過將實參的值複製到形參,來實現按值傳遞到方法,也就是咱們一般說的按值傳遞。複製嘛,總會讓人感受很笨重。好比Matrix4x4這樣比較複雜的值類型,若是直接複製一份新的,反而不如將值類型的引用傳遞給方法做爲參數。

    好啦,CPU的部分匹夫以爲到此就介紹的差很少了。下面就簡單聊聊其實匹夫並非十分熟悉的部分,GPU的優化。

    0x03 GPU的優化

    GPU與CPU不一樣,因此側重點天然也不同。GPU的瓶頸主要存在在以下的方面:

    • 填充率,能夠簡單的理解爲圖形處理單元每秒渲染的像素數量。

    • 像素的複雜度,好比動態陰影,光照,複雜的shader等等

    • 幾何體的複雜度(頂點數量)

    • 固然還有GPU的顯存帶寬

    那麼針對以上4點,其實仔細分析咱們就能夠發現,影響的GPU性能的無非就是2大方面,一方面是頂點數量過多,像素計算過於複雜。另外一方面就是GPU的顯存帶寬。那麼針鋒相對的兩方面舉措也就十分明顯了。

    • 減小頂點數量,簡化計算複雜度。

    • 壓縮圖片,以適應顯存帶寬。

    減小繪製的數目

    那麼第一個方面的優化也就是減小頂點數量,簡化複雜度,具體的舉措就總結以下了:

    • 保持材質的數目儘量少。這使得Unity更容易進行批處理。

    • 使用紋理圖集(一張大貼圖裏包含了不少子貼圖)來代替一系列單獨的小貼圖。它們能夠更快地被加載,具備不多的狀態轉換,並且批處理更友好。

    • 若是使用了紋理圖集和共享材質,使用Renderer.sharedMaterial 來代替Renderer.material 。

    • 使用光照紋理(lightmap)而非實時燈光。

    • 使用LOD,好處就是對那些離得遠,看不清的物體的細節能夠忽略。

    • 遮擋剔除(Occlusion culling)

    • 使用mobile版的shader。由於簡單。

    優化顯存帶寬

    第二個方向呢?壓縮圖片,減少顯存帶寬的壓力。

    • OpenGL ES 2.0使用ETC1格式壓縮等等,在打包設置那裏都有。

    • 使用mipmap。

    MipMap

    這裏匹夫要着重介紹一下MipMap究竟是啥。由於有人說過MipMap會佔用內存呀,但爲什麼又會優化顯存帶寬呢?那就不得不從MipMap是什麼開始聊起。一張圖其實就能解決這個疑問。

    上面是一個mipmap 如何儲存的例子,左邊的主圖伴有一系列逐層縮小的備份小圖

    是否是很一目瞭然呢?Mipmap中每個層級的小圖都是主圖的一個特定比例的縮小細節的複製品。由於存了主圖和它的那些縮小的複製品,因此內存佔用會比以前大。可是爲什麼又優化了顯存帶寬呢?由於能夠根據實際狀況,選擇適合的小圖來渲染。因此,雖然會消耗一些內存,可是爲了圖片渲染的質量(比壓縮要好),這種方式也是推薦的。

    0x04 內存的優化

    既然要聊Unity3D運行時候的內存優化,那咱們天然首先要知道Unity3D遊戲引擎是如何分配內存的。大概能夠分紅三大部分:

    • Unity3D內部的內存

    • Mono的託管內存

    • 若干咱們本身引入的DLL或者第三方DLL所須要的內存。

    第3類不是咱們關注的重點,因此接下來咱們會分別來看一下Unity3D內部內存和Mono託管內存,最後還將分析一個官網上Assetbundle的案例來講明內存的管理。

    Unity3D內部內存

    Unity3D的內部內存都會存放一些什麼呢?各位想想,除了用代碼來驅動邏輯,一個遊戲還須要什麼呢?對,各類資源。因此簡單總結一下Unity3D內部內存存放的東西吧:

    • 資源:紋理、網格、音頻等等

    • GameObject和各類組件。

    • 引擎內部邏輯須要的內存:渲染器,物理系統,粒子系統等等

    Mono託管內存

    由於咱們的遊戲腳本是用C#寫的,同時還要跨平臺,因此帶着一個Mono的託管環境顯然必須的。那麼Mono的託管內存天然就不得不放到內存的優化範疇中進行考慮。那麼咱們所說的Mono託管內存中存放的東西和Unity3D內部內存中存放的東西究竟有何不一樣呢?其實Mono的內存分配就是很傳統的運行時內存的分配了:

    • 值類型:int型啦,float型啦,結構體struct啦,bool啦之類的。它們都存放在堆棧上(注意額,不是堆因此不涉及GC)。

    • 引用類型:其實能夠狹義的理解爲各類類的實例。好比遊戲腳本中對遊戲引擎各類控件的封裝。其實很好理解,C#中確定要有對應的類去對應遊戲引擎中的控件。那麼這部分就是C#中的封裝。因爲是在堆上分配,因此會涉及到GC。

    而Mono託管堆中的那些封裝的對象,除了在在Mono託管堆上分配封裝類實例化以後所須要的內存以外,還會牽扯到其背後對應的遊戲引擎內部控件在Unity3D內部內存上的分配。

    舉一個例子:

    一個在.cs腳本中聲明的WWW類型的對象www,Mono會在Mono託管堆上爲www分配它所須要的內存。同時,這個實例對象背後的所表明的引擎資源所須要的內存也須要被分配。

    一個WWW實例背後的資源:

    • 壓縮的文件

    • 解壓縮所需的緩存

    • 解壓縮以後的文件

    如圖:

    那麼下面就舉一個AssetBundle的例子:

    Assetbundle的內存處理

    如下載Assetbundle爲例子,聊一下內存的分配。匹夫從官網的手冊上找到了一個使用Assetbundle的情景以下:

    IEnumerator DownloadAndCache (){
            // Wait for the Caching system to be ready
            while (!Caching.ready)
                yield return null;
    
            // Load the AssetBundle file from Cache if it exists with the same version or download and store it in the cache
            using(WWW www = WWW.LoadFromCacheOrDownload (BundleURL, version)){
                yield return www; //WWW是第1部分
                if (www.error != null)
                    throw new Exception("WWW download had an error:" + www.error);
                AssetBundle bundle = www.assetBundle;//AssetBundle是第2部分
                if (AssetName == "")
                    Instantiate(bundle.mainAsset);//實例化是第3部分
                else
                    Instantiate(bundle.Load(AssetName));
                        // Unload the AssetBundles compressed contents to conserve memory
                        bundle.Unload(false);
    
            } // memory is freed from the web stream (www.Dispose() gets called implicitly)
        }
    }

    內存分配的三個部分匹夫已經在代碼中標識了出來:

    • Web Stream:包括了壓縮的文件,解壓所需的緩存,以及解壓後的文件。

    • AssetBundle:Web Stream中的文件的映射,或者說引用。

    • 實例化以後的對象:就是引擎的各類資源文件了,會在內存中建立出來。

    那就分別解析一下:

    WWW www = WWW.LoadFromCacheOrDownload (BundleURL, version)
    • 將壓縮的文件讀入內存中

    • 建立解壓所需的緩存

    • 將文件解壓,解壓後的文件進入內存

    • 關閉掉爲解壓建立的緩存

    AssetBundle bundle = www.assetBundle;

    • AssetBundle此時至關於一個橋樑,從Web Stream解壓後的文件到最後實例化建立的對象之間的橋樑。

    • 因此AssetBundle實質上是Web Stream解壓後的文件中各個對象的映射。而非真實的對象。

    • 實際的資源還存在Web Stream中,因此此時要保留Web Stream。

    Instantiate(bundle.mainAsset);

    • 經過AssetBundle獲取資源,實例化對象

    最後各位可能看到了官網中的這個例子使用了:

    `using(WWW www = WWW.LoadFromCacheOrDownload (BundleURL, version)){
    }`

    這種using的用法。這種用法其實就是爲了在使用完Web Stream以後,將內存釋放掉的。由於WWW也繼承了idispose的接口,因此可使用using的這種用法。其實至關於最後執行了:

    //刪除Web Stream
    www.Dispose();
    
    OK,Web Stream被刪除掉了。那還有誰呢?對Assetbundle。那麼使用
    
    //刪除AssetBundle
    bundle.Unload(false);

    使用Unity Profiler工具檢測內存


    在Profiler工具中提供了兩種模式供咱們監測內存的使用狀況,即簡易模式和詳細模式。在簡易模式中,咱們能夠看到總的內存(total)列出了兩列,即Used Total(使用總內存)和Reserved Total(預約總內存)。Used Total和Reserved 均是物理內存,其中Reserved是unity向系統申請的總內存,Unity底層爲了避免常常向系統申請開闢內存,開啓了較大一塊內存做爲緩存,即所謂的Reserved內存,而運行時,unity所使用的內存首先是向Reserved中來申請內存,當不使用時也是先向Reserved中釋放內存,從而來保證遊戲運行的流暢性。通常來講,Used Total越大,則Reserved Total越大,而當Used Total降下去後,Reserved Total也是會隨之降低的(但並不必定與Used Total同步)。

    Unity3D的內存從大致上能夠分爲如下幾個部分:

    • Unity:位Unity3D的底層代碼所分配的內存。

    • Mono:即託管堆。Mono運行時在運行遊戲腳本時所須要的內存,換句話說託管堆的大小與咱們的GameObject數量、資源量無關,僅是腳本代碼形成的。這部份內存是有垃圾回收機制的。

    • GfxDriver:能夠理解爲GPU顯存開銷,主要由Texture,Vertex buffer以及index buffer組成。因此儘量地減小或釋放Texture和mesh等資源,便可下降GfxDriver內存。

    • FMOD:音頻的內存開銷。

    • Profiler

    而在簡易模式下的監視器最下方,則列出了常見的一些資源以及它們所消耗的內存。

    • 紋理

    • 網格

    • 材質

    • 動做

    • 音頻

    • 遊戲對象的數量

    而詳細模式則須要點擊「Take Sample」按鈕來捕獲詳細的內存使用狀況。須要注意的是,因爲得到數據須要花費必定的時間,所以咱們沒法得到實時的詳細內存的使用狀況。在詳細模式中,咱們能夠觀察每一個具體資源和遊戲對象的內存使用狀況。

    相關文章
    相關標籤/搜索