unity3d性能:GC分析2

unity?

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

unity3d性能:GC分析

文章懊蒸。本文分析的幾個點:

1 緩存

2 定時器

3?對象池

4 數(shù)組

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

6 減少單次GC的運行時間



緩存

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

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

void OnTriggerEnter()

{

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

ExampleFunction2(objList);

}

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

public ?Transform[ ] objList;

void Start()

{

objList = Transform.GetChildrens();

}

void OnTriggerEnter()

{

ExampleFunction(objList);

}

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

void Update()

{

Test(transform.postion);

}

void Test()

{

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

box.tranfrom.positon = transfrom.postion;

}


定時器

上述代碼表示惠奸,在綁定該腳本的物體的運行軌跡上,不斷的創(chuàng)建box物體恰梢,又可以是為了畫出物體的運動軌跡或者汽車的車胤之類的功能佛南,當然寫法是不好的,此處只為示例嵌言。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;

}

上述代碼保證了,當物體位置不斷的時候呀页,不會創(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)生。


對象池

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


List.Clear()

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

Update()

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

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

?}竿秆、

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

public List m_objListe;

Update()

{ ?

? m_objList.Clear()

? ?Test(m_objListe);

}


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


string字符串

C#中string為引用類型龟再,分配到heap上,所以需要GC來清理回收尼变。項目開發(fā)中對string的操作非常多利凑,不注意會導致內(nèi)存垃圾。string變量定義賦值后嫌术,如string name = "xiaoTang"哀澈,name的值“xiaoTang”是不可更改的,如name +=" 3 years old"度气;這樣會產(chǎn)生一個新的string name = "xiaoTang 3 years old";而“xiaoTang”這個舊的string還是存在于內(nèi)存中割按,作為一個單獨的變量,沒有被使用磷籍,變成了內(nèi)存垃圾适荣。這就是字符串容易產(chǎn)生內(nèi)存垃圾的原理现柠。那么如果游戲中Text總的內(nèi)容是一個顯示變化的標簽,就更容產(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個單獨的Text该编。

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

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

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進行比較膘掰,避免垃圾產(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類型被當作參數(shù)傳入需要object類型參數(shù)的函數(shù)凡伊。string.Format(string,Object);

voidExampleFunction()

{int cost =5;

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

}

cost變量進行了裝箱操作。裝箱操作會在堆內(nèi)存上分配一個system.Object空間來緩存變量惭聂,這個緩存變量就是垃圾窗声。當然,函數(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用來控制主線程之外的獨立任務(wù)羹呵,或者用以控制代碼執(zhí)行順序骂际。替代方法很多,多個事件之間可以利用委托事件方法實現(xiàn)控制代碼執(zhí)行順序冈欢。

Foreach()

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


如何降低單次GC的運行時間?

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

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

struct類型是值類型,如果里面包含引用類型镜盯,GC就必須檢查struct里面所有的對象岸裙,包含值類型。如下速缆,name導致GC 的時候struct結(jié)構(gòu)體中所有對象都必須進行檢查降允。

public struct ItemData ? ? ?//值類型

{

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

public int cost;

public Vector3 position;

}

private ItemData[] itemData;

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


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

publicclassDialogData

{privateDialogData nextDialog;publicDialogData GetNextDialog()

{returnnextDialog;

}

}

替代代碼:

publicclassDialogData

{privateintnextDialogID;publicintGetNextDialogID()

{returnnextDialogID;

}

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末黑界,一起剝皮案震驚了整個濱河市管嬉,隨后出現(xiàn)的幾起案子皂林,更是在濱河造成了極大的恐慌,老刑警劉巖蚯撩,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件础倍,死亡現(xiàn)場離奇詭異,居然都是意外死亡胎挎,警方通過查閱死者的電腦和手機沟启,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來犹菇,“玉大人德迹,你說我怎么就攤上這事∠罾福” “怎么了浦辨?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長沼沈。 經(jīng)常有香客問我,道長币厕,這世上最難降的妖魔是什么列另? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮旦装,結(jié)果婚禮上页衙,老公的妹妹穿的比我還像新娘。我一直安慰自己阴绢,他們只是感情好店乐,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著呻袭,像睡著了一般眨八。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上左电,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天廉侧,我揣著相機與錄音,去河邊找鬼篓足。 笑死段誊,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的栈拖。 我是一名探鬼主播连舍,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼涩哟!你這毒婦竟也來了索赏?” 一聲冷哼從身側(cè)響起盼玄,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎参滴,沒想到半個月后强岸,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡砾赔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年蝌箍,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片暴心。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡妓盲,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出专普,到底是詐尸還是另有隱情悯衬,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布檀夹,位于F島的核電站筋粗,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏炸渡。R本人自食惡果不足惜娜亿,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蚌堵。 院中可真熱鬧买决,春花似錦、人聲如沸吼畏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽泻蚊。三九已至躲舌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間藕夫,已是汗流浹背孽糖。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留毅贮,地道東北人办悟。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像滩褥,于是被迫代替她去往敵國和親病蛉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容