unity3d性能:GC分析2

unity?

本文講解針對GC的優(yōu)化具體的操作,針對GC的產(chǎn)生以及原理不清晰的可以查看

unity3d性能:GC分析

文章。本文分析的幾個點(diǎn):

1 緩存

2 定時器

3?對象池

4 數(shù)組

5 減少不必要內(nèi)存垃圾

6 減少單次GC的運(yùn)行時間



緩存

? ? 在函數(shù)中,如果使用數(shù)組并且不需要返回該數(shù)組作為函數(shù)結(jié)果,就會產(chǎn)生不必要的堆內(nèi)存垃圾,這時可以在函數(shù)外對函數(shù)進(jìn)行緩存操作。

例如如下代碼,每次調(diào)用都會產(chǎn)生一個新的數(shù)組,造成內(nèi)存分配,使用完該函數(shù)會導(dǎo)致產(chǎn)生內(nèi)存垃圾,主要是每次都用都產(chǎn)生一個。

void OnTriggerEnter()

{

Transform[ ] ?objLists = Transform.GetChildrens();

ExampleFunction2(objList);

}

然而如下代碼,通過緩存一個數(shù)組,則每次調(diào)用函數(shù)都不會進(jìn)行新的內(nèi)存分配,重復(fù)使用緩存的數(shù)組,從而減少了內(nèi)存垃圾的產(chǎn)生。

public ?Transform[ ] objList;

void Start()

{

objList = Transform.GetChildrens();

}

void OnTriggerEnter()

{

ExampleFunction(objList);

}

另外在update()/LateUpdate()等函數(shù)中,添加條件判斷以及計(jì)時器用來減少堆內(nèi)存分配。

void Update()

{

Test(transform.postion);

}

void Test()

{

GameObject ?box = Instanist("Prefabname") as gameObject;

box.tranfrom.positon = transfrom.postion;

}


定時器

上述代碼表示,在綁定該腳本的物體的運(yùn)行軌跡上,不斷的創(chuàng)建box物體,又可以是為了畫出物體的運(yùn)動軌跡或者汽車的車胤之類的功能,當(dāng)然寫法是不好的,此處只為示例。update中不斷的創(chuàng)建box,不斷的產(chǎn)生堆內(nèi)存垃圾。改為如下代碼:

public Vector3 PreviousPostion;

Update()

{

? ? if(tranform.postion ! = PreviousPostion)

{Test(tranform.positon);}

}


void Test()

{?

GameObject? box = Instanist("Prefabname") as gameObject;}

box.tranfrom.positon = transfrom.postion;

PreviousPostion = transform.postion;

}

上述代碼保證了,當(dāng)物體位置不斷的時候,不會創(chuàng)建新的box,有效的降低了內(nèi)存垃圾的產(chǎn)生頻率。如果再加上一個定時器如下:

public float Time =2;

Update()

{

Time -= time.deltaTime;

if(Time <= 0)

{

if(transform.postion != PreviousPostion)

{

Test(transform.postion);

}

Time = 2;

}

}

通過條件判斷,定時器來限制Update()函數(shù)中某些容易產(chǎn)生對內(nèi)存分配的執(zhí)行頻率,可以有效的降低內(nèi)存垃圾的產(chǎn)生。


對象池

另外一點(diǎn),對于unity3d中生命周期函數(shù)update()中,盡量避免創(chuàng)建引用類型,可以在變量聲明處進(jìn)行緩存,此時如果必須要進(jìn)行對象的重復(fù)創(chuàng)建銷毀,例如王者榮耀中,英雄不斷的出生死亡,暴君小兵的銷毀創(chuàng)建,針對此類問題,需要我們經(jīng)常使用的對象池概念object-Pool。不理解可查看unity中對象池(引用文章)對象池的意義在于,減少對內(nèi)存的垃圾,減少GC,提升游戲性能。對象池對應(yīng)的單例設(shè)計(jì)模式在上文中有詳細(xì)介紹,再次不做細(xì)致應(yīng)用講解。另一篇官方的對象池相關(guān)的文章Unity:Object Pooling,是一篇不錯的從本質(zhì)上理解對象池概念的文章。


List.Clear()

這個是老生常談的問題了。游戲開發(fā)中很多時候有些不注意的地方容易產(chǎn)生多余的數(shù)組分配,導(dǎo)致不必要的內(nèi)存垃圾。下列代碼中m_objList列表每次使用都需要重新創(chuàng)建一個,重新分配一段堆內(nèi)存,產(chǎn)生巨大垃圾。

Update()

{ ? ? ? ?m_objList = new List();?

? ? ? ? ?Test(m_objList); ? ? ?

?}、

改為如下代碼,只需要創(chuàng)建一個列表,每次需要使用的時候,Clear()一些就可以當(dāng)作新的列表使用了,類似于對象池的概念,重復(fù)利用率達(dá)到最高,盡量減少內(nèi)存垃圾,減少GC調(diào)用的次數(shù),單次GC的運(yùn)行時間。

public List m_objListe;

Update()

{ ?

? m_objList.Clear()

? ?Test(m_objListe);

}


減少不必要內(nèi)存垃圾


string字符串

C#中string為引用類型,分配到heap上,所以需要GC來清理回收。項(xiàng)目開發(fā)中對string的操作非常多,不注意會導(dǎo)致內(nèi)存垃圾。string變量定義賦值后,如string name = "xiaoTang",name的值“xiaoTang”是不可更改的,如name +=" 3 years old";這樣會產(chǎn)生一個新的string name = "xiaoTang 3 years old";而“xiaoTang”這個舊的string還是存在于內(nèi)存中,作為一個單獨(dú)的變量,沒有被使用,變成了內(nèi)存垃圾。這就是字符串容易產(chǎn)生內(nèi)存垃圾的原理。那么如果游戲中Text總的內(nèi)容是一個顯示變化的標(biāo)簽,就更容產(chǎn)生巨大的垃圾了。如下面代碼所示。

public Text timerText;

private float timer;

void Update()

{

timer += Time.deltaTime;

timerText.text = "Time:"+ timer.ToString();

}、

更改后,變化的部分和不變的部分分離,代碼得到很大優(yōu)化。

public Text timerHeaderText;

public Text timerValueText;

private float timer;

void Start()

{

timerHeaderText.text = "TIME:";

}

void Update()

{

timerValueText.text = timer.ToString();

}

PS:針對string的Key:

1 降低字符串創(chuàng)建次數(shù):1個string被多次使用就要緩存起來,降低string多次創(chuàng)建。

2 降低字符串操作次數(shù):Text中的不變部分與可變部分分離開,作為2個單獨(dú)的Text。

3 StringBiulderClasss:代替string進(jìn)行字符串實(shí)時創(chuàng)建。stringBuilderCalss專門針對不需要內(nèi)存分配設(shè)計(jì)。

4 Debug.Log()函數(shù):即使輸出為空,也會創(chuàng)建至少1個空字符串,項(xiàng)目中大量使用輸出,需注意。

Unity3d函數(shù)調(diào)用

編寫代碼時,調(diào)用插件/工具/引擎代碼中函數(shù)時,容易產(chǎn)生內(nèi)存garbage。本節(jié)列舉部分:

Mesh.normals:迭代器中不斷使用myMesh.normals,函數(shù)每次返回會創(chuàng)建一個數(shù)組用以存放結(jié)果。

voidExampleFunction()

{for(inti=0; i < myMesh.normals.Length;i++)

{

Vector3 normal=myMesh.normals[i];

}

}

同樣功能,替代代碼:只產(chǎn)生了一個垃圾,替代了迭代器中多個垃圾。

voidExampleFunction()

{

Vector3[] meshNormals=myMesh.normals;

for(inti=0; i < meshNormals.Length;i++)

{

Vector3 normal=meshNormals[i];

}

}

GameObject.name/GameObject.tag/input.touches/Phycis.PhereCastNonAlloc():這些函數(shù)都會產(chǎn)生用以返回結(jié)果的應(yīng)用類型內(nèi)存垃圾,unity提供了替代函數(shù)避免垃圾產(chǎn)生;

privatestringplayerTag="Player";

void OnTriggerEnter(Collider other)

{

boolisPlayer = other.gameObject.tag ==playerTag;

}

替代代碼:gameObject.CompareTag()函數(shù)替代GameObject.Tag進(jìn)行比較,避免垃圾產(chǎn)生。

privatestringplayerTag ="Player";

voidOnTriggerEnter(Collider other)

{

boolisPlayer =other.gameObject.CompareTag(playerTag);

}


用Input.GetTouch()和Input.touchCount()來代替Input.touches

Physics.SphereCastNonAlloc()來代替Physics.SphereCastAll()。

裝箱

裝箱操作指:值類型被用作應(yīng)用類型時內(nèi)部變化過程。如:int類型被當(dāng)作參數(shù)傳入需要object類型參數(shù)的函數(shù)。string.Format(string,Object);

voidExampleFunction()

{int cost =5;

string displayString = String.Format("Price:{0} gold",cost);

}

cost變量進(jìn)行了裝箱操作。裝箱操作會在堆內(nèi)存上分配一個system.Object空間來緩存變量,這個緩存變量就是垃圾。當(dāng)然,函數(shù)調(diào)用中,很多插件工具閉包中的代碼我們不知道,調(diào)用的時候很容易產(chǎn)生垃圾,需要我們盡量避免。

協(xié)程(StartCoroutine())

yield return 0;會產(chǎn)生垃圾,0作為int值類型傳入了需要object引用類型個的函數(shù)中,產(chǎn)生了裝箱操作。yield return null替代就不會有問題。

對于協(xié)程,unity用來控制主線程之外的獨(dú)立任務(wù),或者用以控制代碼執(zhí)行順序。替代方法很多,多個事件之間可以利用委托事件方法實(shí)現(xiàn)控制代碼執(zhí)行順序。

Foreach()

unity5版本之間,每一次迭代會產(chǎn)生一個object引用對象垃圾,是由于裝箱操作引起的。5版本之后,unity修復(fù)了此問題,再次不在贅述。


如何降低單次GC的運(yùn)行時間?

該部分主要講重構(gòu)代碼:

每次GC,需要檢查所有的堆內(nèi)存上的對象,引用類型對象的數(shù)量要降低。

struct類型是值類型,如果里面包含引用類型,GC就必須檢查struct里面所有的對象,包含值類型。如下,name導(dǎo)致GC 的時候struct結(jié)構(gòu)體中所有對象都必須進(jìn)行檢查。

public struct ItemData ? ? ?//值類型

{

public ? ? ?string ? ? ?name; ? //引用類型

public int cost;

public Vector3 position;

}

private ItemData[] itemData;

重構(gòu)代碼的方式,是把struct拆分開,這里根據(jù)具體需要做決定,有些時候這樣的重構(gòu)方式導(dǎo)致程序可讀性差,讀者自己權(quán)衡。


如下代碼,GC檢查dialogData的時候,會檢查nextDialoglog對象。替代代碼中用ID替代了對象實(shí)體的引用,避免了檢查次數(shù)的增加。

publicclassDialogData

{privateDialogData nextDialog;publicDialogData GetNextDialog()

{returnnextDialog;

}

}

替代代碼:

publicclassDialogData

{privateintnextDialogID;publicintGetNextDialogID()

{returnnextDialogID;

}

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容