日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > C# >内容正文

C#

Unity开发者的C#内存管理(中篇)

發(fā)布時(shí)間:2024/1/8 C# 52 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Unity开发者的C#内存管理(中篇) 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

第一篇介紹了在?.NET/Mono?和Unity里內(nèi)存管理的基礎(chǔ),并且提供了一些避免不必要的堆分配的建議。第三篇會(huì)深入到對(duì)象池。所有的都主要是面向中級(jí)的C#開(kāi)發(fā)者。

我們現(xiàn)在來(lái)看看兩種發(fā)現(xiàn)項(xiàng)目中不想要的堆分配的方法。第一種-Unity?profiler-實(shí)在是太簡(jiǎn)單了,但是卻相當(dāng)費(fèi)錢,得買’pro‘版的。第二種是講你的.NET/Mono程序集反匯編成中間語(yǔ)言(CIL)然后再檢查。如果你從沒(méi)見(jiàn)過(guò)反匯編的.NET代碼,繼續(xù)看下去,不難,而且免費(fèi)還很有啟發(fā)意義。

容易的方法:使用Unity?profiler

Unity優(yōu)秀的分析器主要被用來(lái)分析游戲中各種資源需要的性能和資源:著色器,紋理,音頻,游戲?qū)ο蟮鹊?。然而分析器在發(fā)掘內(nèi)存上也一樣有用-跟你的C#代碼的行為有關(guān)-甚至是外部的?沒(méi)引用UnityEngine.dll的.NET/Mono程序集!在當(dāng)前Unity版本中(4.3),這個(gè)功能不是來(lái)自內(nèi)存分析器,而是CPU分析器。到C#代碼的時(shí)候,內(nèi)存分析器只是展示Mono堆的總大小和已使用的量。

這樣讓你看你的C#代碼是否有嫩村泄露實(shí)在太粗糙了。即使不適用任何腳本,已使用的堆大小也會(huì)持續(xù)增長(zhǎng)和縮減。只要你使用腳本,你需要一個(gè)看哪里分配了內(nèi)存的途徑,然后CPU分析器剛好給你提供這個(gè)。

讓我們來(lái)看看一些實(shí)例代碼。假設(shè)下面的腳本綁定到了一個(gè)GameObject上。

?

using UnityEngine;using System.Collections.Generic; public class MemoryAllocatingScript : MonoBehaviour{ void Update() { List<int> iList = new List<int>(new int[] { 072, 101, 108, 108, 111, 032, 119, 111, 114, 108, 100, 033 }); string result = ""; foreach (int i in iList.ToArray()) result += ((char)i).ToString(); Debug.Log(result); }}

?

它所做的就是通過(guò)一組整數(shù)用一種繞的方法創(chuàng)建了一個(gè)字符串("Hello?world!"),一路上造成了不必要的內(nèi)存分配。多少呢?很高興你問(wèn)了,但是我很懶,就讓我們看看CPU分析器吧。選中窗口頂部的”Deep?Profiler“,可以跟蹤到每幀的調(diào)用樹(shù)。

正如你所見(jiàn),堆內(nèi)存在Update()函數(shù)過(guò)程中的5個(gè)不同位置被分配。這個(gè)列表的初始化,foreach循環(huán)里到數(shù)組的轉(zhuǎn)換是多余的,每一個(gè)數(shù)字到字符的轉(zhuǎn)換以及連接都需要分配內(nèi)存。有趣的是,僅僅是調(diào)用Debug.Log()也會(huì)分配一大塊內(nèi)存-這點(diǎn)值得記下來(lái),即使在生產(chǎn)環(huán)境中這段代碼會(huì)被剔除。

如果你沒(méi)有Unity?Pro,但是恰巧有Microsoft?Visual?Studio,那就有替代Unity?Profiler的方法來(lái)發(fā)掘調(diào)用堆棧。Telerik?告訴我他們的?JustTrace?Memory?profiler?有相似的功能?(see?here).?然而,?我不知道它模仿Unity每幀記錄調(diào)用樹(shù)到了什么程度。更進(jìn)一步,盡管對(duì)Unity項(xiàng)目的遠(yuǎn)程調(diào)試(通過(guò)UnityVS)?是可以的,我還是沒(méi)有成功的把JustTrace用來(lái)分析被Unity調(diào)用的程序集。

只是稍微難一點(diǎn)點(diǎn)的方法:反匯編你的代碼

CIL的背景知識(shí)

如果你已經(jīng)有了一個(gè).NET/Mono的反匯編器,開(kāi)始用吧,不然我推薦ILSpy.?這個(gè)工具不僅是免費(fèi)的,它還非常干凈簡(jiǎn)單,但是剛好包含下面我們會(huì)用到的一個(gè)特殊功能。

你也許知道C#編譯器不會(huì)將你的代碼編譯成機(jī)器語(yǔ)言,而是公共中間語(yǔ)言。這種語(yǔ)言是被原.NET團(tuán)隊(duì)作為一種包含兩種來(lái)自高級(jí)語(yǔ)言特性的低級(jí)語(yǔ)言開(kāi)發(fā)出來(lái)的。一方面,它與硬件無(wú)關(guān),另一方面,它包含最適合被稱為’面向?qū)ο蟆奶匦?比如可以引用其他模塊或者類的能力。

沒(méi)有經(jīng)過(guò)代碼模糊處理(?code?obfuscator?)的CIL代碼是異常容易反向工程的。?許多情況下,結(jié)果幾乎和原始的C#(VB)代碼一樣。ILSpy?可以替你做這件事,但是我們僅僅反匯編代碼就可以了(ILSpy通過(guò)調(diào)用ildasm.exe來(lái)實(shí)現(xiàn),.它是NET/Mono的一部分)。讓我們從一個(gè)加兩個(gè)整數(shù)的函數(shù)開(kāi)始。

int AddTwoInts(int first, int second) { int result = first + second; return result;}

如果你愿意,你可以將這段代碼粘貼到MemoryAllocatingScript.cs文件里。然后確保Unity編譯了它,再用ILSpy打開(kāi)編譯了的庫(kù)Assembly-Csharp.dll。如果你選擇AddTwoInts()?方法,你會(huì)看到下面的:

除了藍(lán)色的關(guān)鍵字?hidebysig,我們可以忽略掉,方法簽名應(yīng)該看起來(lái)差不多。要了解到方法里主要發(fā)生了什么,你需要知道CIL把CPU看成一個(gè)堆棧式機(jī)器stack?machine?而不是寄存器機(jī)器register?machine。CIL假設(shè)CPU可以處理非?;A(chǔ),非常算法的指令,例如”將兩個(gè)整數(shù)相加“,而且它可以處理任何內(nèi)存地址的隨機(jī)訪問(wèn)。CIL還假設(shè)CPU不直接在RAM上進(jìn)行算術(shù)操作,而是首先需要將數(shù)據(jù)裝載進(jìn)概念上的計(jì)算堆棧。(注意計(jì)算堆棧和你你知道的C#堆棧沒(méi)有任何關(guān)系。CIL計(jì)算堆棧只是一個(gè)抽象的,并且預(yù)設(shè)很小。)在行IL_0000到IL_0005發(fā)生了:

  • 兩個(gè)整型參數(shù)被推進(jìn)堆棧。
  • 加法被調(diào)用然后從堆棧里彈出開(kāi)始位置的兩個(gè)對(duì)象,自動(dòng)將記過(guò)壓進(jìn)堆棧。
  • 第3和4行可以忽略,因?yàn)樵诎l(fā)行版本里會(huì)被優(yōu)化掉。
  • 這個(gè)方法返回堆棧的第一個(gè)值。

找到CIL里面的內(nèi)存分配

CIL代碼美在它不會(huì)隱藏任何堆分配。而且,堆分配會(huì)嚴(yán)格按照以下三個(gè)順序分配,在你的反匯編代碼里能看到。

  • newobj?<constructor>:這創(chuàng)建了一個(gè)由constructor指定類型的未初始化的對(duì)象。如果這個(gè)對(duì)象是值類型,它就在堆棧上被創(chuàng)建。如果它是一個(gè)引用類型,就在堆上。你總是能從CIL代碼知道類型,所以你可以容易的知道內(nèi)存分配產(chǎn)生的地方。
  • newarr?<element?type>:這條指令在堆上創(chuàng)建了一個(gè)新的數(shù)組。Element的類型由參數(shù)指定。
  • box?<value?type?token>:這條特殊的指令執(zhí)行裝箱操作,我們已經(jīng)在第一篇帖子里說(shuō)過(guò)。

Let's?look?at?a?rather?contrived?method?that?performs?all?three?types?of?allocations.

然我們來(lái)看一個(gè)人為的執(zhí)行這三種內(nèi)存分配的方法。

?

void SomeMethod() { object[] myArray = new object[1]; myArray[0] = 5; Dictionary<int, int> myDict = new Dictionary<int, int>();myDict[4] = 6; foreach (int key in myDict.Keys) Console.WriteLine(key);}

?

有這幾行代碼產(chǎn)生的CIL代碼很多,所以這里我們只看關(guān)鍵部分:

IL_0001:?newarr?[mscorlib]System.Object...IL_000a:?box?[mscorlib]System.Int32...IL_0010:?newobj?instance?void?class?[mscorlib]System.????Collections.Generic.Dictionary'2<int32,?int32>::.ctor()...IL_001f:?callvirt?instance?class?[mscorlib]System.????Collections.Generic.Dictionary`2/KeyCollection<!0,?!1>????class?[mscorlib]System.Collections.Generic.Dictionary`2<int32,????int32>::get_Keys()

正如我們懷疑過(guò)的,對(duì)象的數(shù)組(SomeMethod()里的第一行)導(dǎo)致newarr指令。整數(shù)5被賦給數(shù)組的第一個(gè)元素需要裝箱。Dictionary<int,?int>是被newobj指令分配的。

但是還有第四個(gè)堆分配!正如我在第一篇帖子里提到的,Dictionary<K,?V>.?KeyCollection被聲明為一個(gè)類,不是結(jié)構(gòu)。這個(gè)類的一個(gè)實(shí)例會(huì)被創(chuàng)建,這樣foreach蓄奴換才有迭代的對(duì)象。不幸的是,分配發(fā)生在Keys屬性的getter方法里。正如你在CIL代碼里看到,這個(gè)方法的名字是get_Keys(),而且它的返回值是一個(gè)類。

作為一個(gè)查找內(nèi)存泄露的通用方法,你可以生成一個(gè)對(duì)你的整個(gè)程序集反匯編的CIL文件,只要在ILSpy按下Ctrl+S。然后用你喜歡的文本編輯器打開(kāi)這個(gè)文件,搜索上面提到的三種指令。查出其他程序集里的內(nèi)存泄露是有難度。我唯一知道的辦法就是仔細(xì)檢查你的C#代碼,確認(rèn)所有的外部方法調(diào)用,并且一個(gè)個(gè)地查看它們的CIL代碼。你怎么知道什么時(shí)候就完成了?很簡(jiǎn)單:你的游戲可以流暢的運(yùn)行好幾個(gè)小時(shí),不因?yàn)槔占斐扇魏蔚男阅芷款i。

PS:在之前的帖子里,我答應(yīng)要向你們展示如何確認(rèn)你們系統(tǒng)上的Mono版本。只要裝了ILSpy,沒(méi)有比這更簡(jiǎn)單的了。在ILSpy里,點(diǎn)擊打開(kāi)然后找到Unity根目錄。找到Data/Mono/lib/mono/2.0然后打開(kāi)mscorlib.dll。在層級(jí)視圖里,找到mscorlib/-/Consts,然后那兒你能找到MonoVersion作為一個(gè)字符串常量。

總結(jié)

以上是生活随笔為你收集整理的Unity开发者的C#内存管理(中篇)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。