最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

了解托管堆

2020-05-21 10:35 作者:unity_某某師_高錦錦  | 我要投稿

許多 Unity 開發(fā)者面臨的另一個常見問題是托管堆的意外擴(kuò)展。在 Unity 中,托管堆的擴(kuò)展比收縮容易得多。此外,Unity 的垃圾收集策略往往會使內(nèi)存碎片化,因此可能阻止大型堆的收縮。

托管堆的工作原理及其擴(kuò)展原因

“托管堆”是由項目腳本運(yùn)行時(Mono 或 IL2CPP)的內(nèi)存管理器自動管理的一段內(nèi)存。必須在托管堆上分配托管代碼中創(chuàng)建的所有對象(2)(__注意:__嚴(yán)格來說,必須在托管堆上分配所有非 null 引用類型對象和所有裝箱值類型對象)。

在上圖中,白框表示分配給托管堆的內(nèi)存量,而其中的彩色框表示存儲在托管堆的內(nèi)存空間中的數(shù)據(jù)值。當(dāng)需要更多值時,將從托管堆中分配更多空間。

垃圾回收器定期運(yùn)行(3)(__注意:__具體運(yùn)行時間視平臺而定)。這時將掃描堆上的所有對象,將任何不再引用的對象標(biāo)記為刪除。然后會刪除未引用的對象,從而釋放內(nèi)存。

至關(guān)重要的是,Unity 的垃圾收集(使用?Boehm GC 算法)是非分代的,也是非壓縮的?!胺欠执币馕吨?GC 在執(zhí)行每遍收集時必須掃描整個堆,因此隨著堆的擴(kuò)展,其性能會下降。“非壓縮”意味著不會為內(nèi)存中的對象重新分配內(nèi)存地址來消除對象之間的間隙。

上圖為內(nèi)存碎片化示例。釋放對象時,將釋放其內(nèi)存。但是,釋放的空間不會整合成為整個“可用內(nèi)存”池的一部分。位于釋放的對象兩側(cè)的對象可能仍在使用中。因此,釋放的空間成為其他內(nèi)存段之間的“間隙”(該間隙由上圖中的紅色圓圈指示)。因此,新釋放的空間僅可用于存儲與釋放相同大小或更小的對象的數(shù)據(jù)。

分配對象時,請注意對象在內(nèi)存空間中的分配地址必須始終為連續(xù)空間塊。

這導(dǎo)致了內(nèi)存碎片化這個核心問題:雖然堆中的可用空間總量可能很大,但是可能其中的部分或全部的可分配空間對象之間存在小的“間隙”。這種情況下,即使可用空間總量高于要分配的空間量,托管堆可能也找不到足夠大的連續(xù)內(nèi)存塊來滿足該分配需求。

但是,如果分配了大型對象,卻沒有足夠的連續(xù)可用空間來容納該對象(如上所示),Unity 內(nèi)存管理器將執(zhí)行兩個操作。

首先,如果垃圾回收器尚未運(yùn)行,則運(yùn)行垃圾回收器。此工具會嘗試釋放足夠的空間來滿足分配請求。

如果在 GC 運(yùn)行后,仍然沒有足夠的連續(xù)空間來滿足請求的內(nèi)存量,則必須擴(kuò)展堆。堆的具體擴(kuò)展量視平臺而定;但是,大多數(shù) Unity 平臺會使托管堆的大小翻倍。

堆的主要問題

托管堆擴(kuò)展方面的核心問題有兩個:

  • Unity 在擴(kuò)展托管堆后不會經(jīng)常釋放分配給托管堆的內(nèi)存頁;它會樂觀地保留擴(kuò)展后的堆,即使該堆的大部分為空時也如此。這是為了防止再次發(fā)生大量分配時需要重新擴(kuò)展堆。

  • 在大多數(shù)平臺上,Unity 最終會將托管堆的空置部分使用的頁面釋放回操作系統(tǒng)。發(fā)生此行為的間隔時間是不確定的,因此不要指望靠這種方法釋放內(nèi)存。

  • 托管堆使用的地址空間始終不會歸還給操作系統(tǒng)。

  • 對于 32 位程序,如果托管堆多次擴(kuò)展和收縮,則可能導(dǎo)致地址空間耗盡。如果一個程序的可用內(nèi)存地址空間已耗盡,操作系統(tǒng)將終止該程序。

  • 對于 64 位程序而言,地址空間足夠大到可以運(yùn)行時間超過人類平均壽命的程序,因此地址空間耗盡的這種情況極幾乎不可能發(fā)生。

臨時分配

許多 Unity 項目在每幀都有幾十或幾百 KB 的臨時數(shù)據(jù)分配給托管堆。這種情況通常對項目的性能極為不利。請考慮以下數(shù)學(xué)計算:

如果一個程序每幀分配一千字節(jié) (1 KB) 的臨時內(nèi)存,并且以每秒 60 幀的速率運(yùn)行,那么它必須每秒分配 60 KB 的臨時內(nèi)存。在一分鐘內(nèi),這會在內(nèi)存中增加 3.6 MB 的垃圾。每秒調(diào)用一次垃圾回收器可能會對性能產(chǎn)生不利影響,但對于內(nèi)存不足的設(shè)備而言每分鐘分配 3.6 MB 的內(nèi)存是個問題。

此外,請考慮加載操作。如果在大型資源加載操作期間生成了大量臨時對象,并且對這些對象的引用一直持續(xù)到操作完成,則垃圾回收器無法釋放這些臨時對象,并且托管堆需要進(jìn)行擴(kuò)展,即使它包含的許多對象將在不久后釋放也是如此。

跟蹤托管內(nèi)存分配情況相對簡單。在 Unity 的 CPU 性能分析器中,Overview 表有一個“GC Alloc”列。此列顯示了在特定幀中的托管堆上分配的字節(jié)數(shù)(4)(__注意:__這與給定幀期間臨時分配的字節(jié)數(shù)不同。性能分析器會顯示特定幀中分配的字節(jié)數(shù),不考慮在后續(xù)幀中是否重用了部分/全部已分配的內(nèi)存)。啟用“Deep Profiling”選項后,可以跟蹤執(zhí)行這些分配的方法。

Unity Profiler 不會跟蹤在主線程之外發(fā)生的分配。因此,“GC Alloc”列不能用于統(tǒng)計用戶創(chuàng)建的線程中發(fā)生的托管分配。請將代碼執(zhí)行從單獨(dú)線程切換到主線程以進(jìn)行調(diào)試,或使用?BeginThreadProfiling?API 在時間軸性能分析器 (Timeline Profiler) 中顯示例程。

務(wù)必使用目標(biāo)設(shè)備上的開發(fā)版來分析托管分配。

請注意,某些腳本方法在 Editor 中運(yùn)行時會產(chǎn)生分配內(nèi)存,但在構(gòu)建項目后不會產(chǎn)生分配內(nèi)存。GetComponent?是最常見的示例;此方法始終在 Editor 中執(zhí)行時分配內(nèi)存,而不會在已構(gòu)建的項目中分配內(nèi)存。

通常,強(qiáng)烈建議所有開發(fā)人員在項目處于交互狀態(tài)時最大限度減少托管堆內(nèi)存分配。非交互操作(例如場景加載)期間的內(nèi)存分配很少產(chǎn)生問題。

適用于 Visual Studio 的?Jetbrains Resharper 插件可以幫助找到代碼中的內(nèi)存分配。

使用 Unity 的深度性能分析 (Deep Profile)?模式可找到托管分配的具體原因。在深度性能分析模式下,所有方法調(diào)用都是單獨(dú)記錄的,可更清晰地查看方法調(diào)用樹中發(fā)生托管分配的位置。請注意,深度性能分析模式不僅可在 Editor 中使用,還可借助命令行參數(shù)?-deepprofiling?在 Android 和桌面平臺上運(yùn)行。Deep Profiler 按鈕在性能分析期間保持灰色。

基本的內(nèi)存節(jié)省方法

可使用一些相對簡單的技術(shù)來減少托管堆分配。

集合和數(shù)組重用

使用 C# 的集合類或數(shù)組時,盡可能考慮重用或匯集已分配的集合或數(shù)組。集合類開放了一個?Clear?方法,該方法會消除集合內(nèi)的值,但不會釋放分配給集合的內(nèi)存。

void Update() { ? ?

List<float> nearestNeighbors = new List<float>(); ? findDistancesToNearestNeighbors(nearestNeighbors); ? ?

nearestNeighbors.Sort(); ? ?

// … 以某種方式使用排序列表 …?

}

在為復(fù)雜計算分配臨時“helper”集合時,這尤其有用。下面的代碼是一個非常簡單的示例:

在此示例中,為了收集一組數(shù)據(jù)點(diǎn),每幀都為?nearestNeighbors?List(列表)分配一次內(nèi)存。將此 List 從方法中提升到包含類中是非常簡單的,這樣做避免了每幀都為新 List 分配內(nèi)存:

List<float> m_NearestNeighbors = new List<float>();?

void Update() { ? ?

m_NearestNeighbors.Clear(); ??

findDistancesToNearestNeighbors(NearestNeighbors); ? ?

m_NearestNeighbors.Sort(); ? ?

// … 以某種方式使用排序列表 …?

}

在此版本中,List 的內(nèi)存被保留并在多個幀之間重用。僅在 List 需要擴(kuò)展時才分配新內(nèi)存。

閉包和匿名方法

使用閉包和匿名方法時需要注意兩點(diǎn)。

首先,C# 中的所有方法引用都是引用類型,因此在堆上進(jìn)行分配。通過將方法引用作為參數(shù)傳遞,可以輕松分配臨時內(nèi)存。無論傳遞的方法是匿名方法還是預(yù)定義的方法,都會發(fā)生此分配。

其次,將匿名方法轉(zhuǎn)換為閉包后,為了將閉包傳遞給接收閉包的方法,所需的內(nèi)存量將顯著增加。

請參考以下代碼:

List<float> listOfNumbers = createListOfRandomNumbers();?

listOfNumbers.Sort( (x, y) => (int)x.CompareTo((int)(y/2)) );

這段代碼使用簡單的匿名方法來控制在第一行創(chuàng)建的數(shù)字列表的排序順序。但是,如果程序員希望使該代碼段可重用,很容易想到將常量?2?替換為局部作用域內(nèi)的變量,如下所示:

List<float> listOfNumbers = createListOfRandomNumbers();?

int desiredDivisor = getDesiredDivisor();?

listOfNumbers.Sort( (x, y) => (int)x.CompareTo((int)(y/desiredDivisor)) );

匿名方法現(xiàn)在要求該方法能夠訪問方法作用域之外的變量狀態(tài),因此已成為閉包。必須以某種方式將?desiredDivisor?變量傳遞給閉包,以便閉包的實(shí)際代碼可以使用該變量。

為此,C# 將生成一個匿名類,該類可保存閉包所需的外部作用域變量。當(dāng)閉包傳遞給?Sort?方法時,將實(shí)例化此類的副本,并用?desiredDivisor?整數(shù)的值初始化該副本。

因為執(zhí)行閉包需要實(shí)例化閉包生成類的副本,并且所有類都是 C# 中的引用類型,所以執(zhí)行閉包需要在托管堆上分配對象。

通常,請盡可能在 C# 中避免使用閉包。應(yīng)在性能敏感的代碼中盡可能減少匿名方法和方法引用,尤其是那些每幀都需要執(zhí)行的代碼中。

IL2CPP 下的匿名方法

目前,通過查看 IL2CPP 所生成的代碼得知,對System.Function?類型變量的聲明和賦值將會分配一個新對象。無論變量是顯式的(在方法/類中聲明)還是隱式的(聲明為另一個方法的參數(shù)),都是如此。

因此,使用 IL2CPP 腳本后端下的匿名方法必定會分配托管內(nèi)存。在 Mono 腳本后端下則不是這種情況。

此外,由于方法參數(shù)的聲明方式不同,將導(dǎo)致IL2CPP 顯示出托管內(nèi)存分配量產(chǎn)生巨大差異。正如預(yù)期的那樣,閉包的每次調(diào)用會消耗最多的內(nèi)存。

預(yù)定義的方法在 IL2CPP 腳本后端下作為參數(shù)傳遞時,其__分配的內(nèi)存幾乎與閉包一樣多__,但這不是很直觀。匿名方法在堆上生成最少量的臨時垃圾(一個或多個數(shù)量級)。

因此,如果打算在 IL2CPP 腳本后端上發(fā)布項目,有三個主要建議:

  • 最好選擇不需要將方法作為參數(shù)傳遞的編碼風(fēng)格。

  • 當(dāng)不可避免時,最好選擇匿名方法而不是預(yù)定義方法。

  • 無論腳本后端為何,都要避免使用閉包。

裝箱 (Boxing)

裝箱是 Unity 項目中最常見的非預(yù)期臨時內(nèi)存分配來源之一。只要將值類型的值用作引用類型就會發(fā)生裝箱;這種情況最常發(fā)生在將原始值類型的變量(例如?int?和?float)傳遞給對象類型的方法時。

在下面非常簡單的示例中,對?x?中的整數(shù)進(jìn)行了裝箱以便傳遞給?object.Equals?方法,因為?object?上的?Equals?方法要求將?object?作為參數(shù)傳遞給它。

int x = 1; object y = new object(); y.Equals(x);

C# IDE(集成開發(fā)環(huán)境)和編譯器通常不會發(fā)出關(guān)于裝箱的警告,即使導(dǎo)致意外的內(nèi)存分配時也是如此。這是因為 C# 語言的設(shè)計理念認(rèn)為,小型臨時分配可以被分代垃圾回收器和對分配大小敏感的內(nèi)存池有效處理。

雖然 Unity 的分配器實(shí)際會使用不同的內(nèi)存池進(jìn)行小型和大型分配,但 Unity 的垃圾回收器“不是”分代的,因此無法有效清除由裝箱生成的小型、頻繁的臨時分配。

在為 Unity 運(yùn)行時編寫 C# 代碼時,應(yīng)盡可能避免使用裝箱。

識別裝箱

裝箱在 CPU 跟蹤中顯示為對某幾種特定方法的調(diào)用,具體形式取決于使用的腳本后端。這些調(diào)用通常采用以下形式之一,其中?<some class>?是其他類或結(jié)構(gòu)的名稱,而?...?是一些參數(shù):

  • <some class>::Box(…)

  • Box(…)

  • <some class>_Box(…)

也可以通過搜索反編譯器或 IL 查看器(例如 ReSharper 中內(nèi)置的 IL 查看器工具或 dotPeek 反編譯器)的輸出來定位裝箱。IL 指令為“box”。

字典和枚舉

裝箱的一個常見原因是使用?enum?類型作為字典的鍵。聲明?enum?會創(chuàng)建一個新值類型,此類型在后臺視為整數(shù),但在編譯時實(shí)施類型安全規(guī)則。

默認(rèn)情況下,調(diào)用?Dictionary.add(key, value)?會導(dǎo)致調(diào)用?Object.getHashCode(Object)。此方法用于獲取字典的鍵的相應(yīng)哈希代碼,并在所有接受鍵的方法中使用,如:Dictionary.tryGetValue, Dictionary.remove?等。

Object.getHashCode?方法為引用類型,但?enum?值始終為值類型。因此,對于枚舉鍵字典,每次方法調(diào)用都會導(dǎo)致鍵被裝箱至少一次。

以下代碼片段展示的一個簡單示例說明了此裝箱問題:

enum MyEnum { a, b, c };?

var myDictionary = new Dictionary<MyEnum, object>();?

myDictionary.Add(MyEnum.a, new object());

要解決此問題,則需要編寫一個實(shí)現(xiàn)?IEqualityComparer?接口的自定義類,并將該類的實(shí)例指定為字典的比較器(__注意:__此對象通常是無狀態(tài)的,因此可與不同的字典實(shí)例一起重復(fù)使用以節(jié)省內(nèi)存)。

以下是上述代碼片段 IEqualityComparer 的簡單示例。

public class MyEnumComparer : IEqualityComparer<MyEnum> { ? ?

public bool Equals(MyEnum x, MyEnum y) { ? ? ? ?

return x == y; ? ?

} ? ?

public int GetHashCode(MyEnum x) { ? ? ? ?

return (int)x; ? ?

} }

可將上述類的實(shí)例傳遞給字典的構(gòu)造函數(shù)。

Foreach 循環(huán)

在 Unity 的 Mono C# 編譯器版本中,使用?foreach?循環(huán)會在每次循環(huán)終止時強(qiáng)制 Unity 將一個值裝箱(__注意:__是在每次整個循環(huán)完整執(zhí)行完成后將該值裝箱一次,并非在循環(huán)的每次迭代中裝箱一次,因此無論循環(huán)運(yùn)行兩次還是 200 次,內(nèi)存使用量都保持不變)。這是因為 Unity 的 C# 編譯器生成的 IL 會構(gòu)造一個通用值類型的枚舉器來遍歷值集合。

此枚舉器將實(shí)現(xiàn)?IDisposable?接口;當(dāng)循環(huán)終止時必須調(diào)用該接口。但是,在值類型的對象(例如結(jié)構(gòu)和枚舉器)上調(diào)用接口方法需要將它們裝箱。

請參考下面非常簡單的示例代碼:

int accum = 0;?

foreach(int x in myList) { ? ?

accum += x;?

}

以上代碼通過 Unity 的 C# 編譯器運(yùn)行后將生成以下中間語言:

.method private hidebysig instance void ? ? ??

ILForeach() cil managed ? ??

{ ? ? ??

.maxstack 8 ? ? ?

.locals init ( ? ? ? ??

[0] int32 num, ? ? ? ??

[1] int32 current, ? ? ? ??

[2] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> V_2 ) ?? // [67 5 - 67 16] ? ? ?

IL_0000: ldc.i4.0 ? ? ? ? ??

IL_0001: stloc.0 ? ? ?// num ? ? ??

// [68 5 - 68 74] ? ? ??

IL_0002: ldarg.0 ? ? ?// this ? ? ??

IL_0003: ldfld ? ? ? ?class [mscorlib]System.Collections.Generic.List`1<int32> test::myList ? ? ? IL_0008: callvirt ? ? instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0/*int32*/> class [mscorlib]System.Collections.Generic.List`1<int32>::GetEnumerator() ? ? ??

IL_000d: stloc.2 ? ? ?// V_2 ? ? ? .try ? ? ??

{

IL_000e: br ? ? ? ? ? IL_001f ? ? ??

// [72 9 - 72 41] ? ? ? ??

IL_0013: ldloca.s ? ? V_2 ? ? ? ??

IL_0015: call ? ? ? ? instance !0/*int32*/ valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::get_Current() ? ? ? ??

IL_001a: stloc.1 ? ? ?// current ? ? ??

// [73 9 - 73 23] ? ? ? ??

IL_001b: ldloc.0 ? ? ?// num ? ? ? ??

IL_001c: ldloc.1 ? ? ?// current ? ? ? ??

IL_001d: add ? ? ? ? ? ? ? ? ??

IL_001e: stloc.0 ? ? ?// num ? ? ??

// [70 7 - 70 36] ? ? ? ??

IL_001f: ldloca.s ? ? V_2 ? ? ? ??

IL_0021: call ? ? ? ? instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext() ? ? ? ??

IL_0026: brtrue ? ? ? IL_0013 ? ? ? ??

IL_002b: leave ? ? ? ?IL_003c ? ? ??

}?

// .try 結(jié)束 ? ? ??

finally ? ? ??

{ ? ? ? ??

IL_0030: ldloc.2 ? ? ?// V_2 ? ? ? ??

IL_0031: box ? ? ? ? ?valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> ? ? ? ??

IL_0036: callvirt ? ? instance void [mscorlib]System.IDisposable::Dispose() ? ? ? ??

IL_003b: endfinally ? ? ? ??

} // finally 結(jié)束 ? ? ??

IL_003c: ret ? ? ? ? ? ? ??

} // 方法 test::ILForeach結(jié)束 ??

} // test 類結(jié)束

最相關(guān)的代碼是靠近底部的?__finally { … }__?代碼塊。callvirt?指令在調(diào)用?IDisposable.Dispose?方法之前先發(fā)現(xiàn)該方法在內(nèi)存中的位置,并要求將枚舉器裝箱。

通常,應(yīng)在 Unity 中避免使用?foreach?循環(huán)。原因不僅是這些循環(huán)會進(jìn)行裝箱,而且通過枚舉器遍歷集合的方法調(diào)用成本更高,通常比通過?for?或?while?循環(huán)進(jìn)行的手動迭代慢得多。

請注意,Unity 5.5 中的 C# 編譯器升級版本顯著提高了 Unity 生成 IL 的能力。特別值得注意的是,已從?foreach?循環(huán)中消除裝箱操作。因此,節(jié)約了與?foreach?循環(huán)相關(guān)的內(nèi)存開銷。但是,由于方法調(diào)用開銷,與基于數(shù)組的等效代碼相比,CPU 性能差距仍然存在。

Unity 數(shù)組值 API

虛數(shù)組分配的一種更有害和更不明顯的原因是重復(fù)訪問返回數(shù)組的 Unity API。返回數(shù)組的所有 Unity API 每次被訪問時都會創(chuàng)建一個新的數(shù)組副本。在不必要的情況下訪問數(shù)組值 Unity API 是極不適宜的。

例如,下面的代碼在每次循環(huán)迭代時都會虛化創(chuàng)建?vertices?數(shù)組的四個副本。每次訪問?.vertices?屬性時都會發(fā)生分配。

for(int i = 0; i < mesh.vertices.Length; i++){? ? ??

float x, y, z; ? ?

x = mesh.vertices[i].x; ? ?

y = mesh.vertices[i].y; ? ?

z = mesh.vertices[i].z; ? ?

// ... ? ?

DoSomething(x, y, z);?

}

通過在進(jìn)入循環(huán)之前捕獲?vertices?數(shù)組,無論循環(huán)迭代次數(shù)是多少,都可以簡單地重構(gòu)為單個數(shù)組分配:

var vertices = mesh.vertices;?

for(int i = 0; i < vertices.Length; i++){?

float x, y, z;

x = vertices[i].x;

y = vertices[i].y; ? ?

z = vertices[i].z; ? ?

// ... ? ?DoSomething(x, y, z); ??

}

雖然訪問一次屬性的 CPU 成本不是很高,但在緊湊循環(huán)內(nèi)重復(fù)訪問會使得 CPU 性能過熱。此外,重復(fù)訪問會導(dǎo)致托管堆出現(xiàn)不必要的擴(kuò)展。

此問題在移動端極其常見,因為?Input.touches?API 的行為與上述類似。項目包含以下類似代碼是極為常見的,此情況下每次訪問?.touches?屬性時都會發(fā)生分配。

for ( int i = 0; i < Input.touches.Length; i++ ) {

Touch touch = Input.touches[i]; ? ?

// … }

當(dāng)然,通過將數(shù)組分配從循環(huán)條件中提升出來,可輕松改善該問題:

Touch[] touches = Input.touches;?

for ( int i = 0; i < touches.Length; i++ )?{

Touch touch = touches[i]; ??

// … }

但是,現(xiàn)在有許多 Unity API 的版本不會導(dǎo)致內(nèi)存分配。如果能使用這些版本時,請盡量選擇這種版本。

int touchCount = Input.touchCount;?

for ( int i = 0; i < touchCount; i++ ){

Touch touch = Input.GetTouch(i); ??

// … }

將上面的示例轉(zhuǎn)換為無分配的 Touch API 很簡單:

請注意,為了節(jié)省調(diào)用屬性的?get?方法的 CPU 成本,屬性訪問 (Input.touchCount) 仍然保持在循環(huán)條件之外。

空數(shù)組重用

當(dāng)數(shù)組值方法需要返回空集時,有些開發(fā)團(tuán)隊更喜歡返回空數(shù)組而不是?null。這種編碼模式在許多托管語言中很常見,特別是 C# 和 Java。

通常情況下,從方法返回零長度數(shù)組時,返回零長度數(shù)組的預(yù)分配單例實(shí)例比重復(fù)創(chuàng)建空數(shù)組要高效得多(5)(__注意:__當(dāng)然,在返回數(shù)組后調(diào)整數(shù)組大小時是個例外)。

腳注

  • (1)?這是因為大多數(shù)平臺上從 GPU 內(nèi)存回讀的速度極慢。將紋理從 GPU 內(nèi)存讀入臨時緩沖區(qū)以供 CPU 代碼(例如?Texture.GetPixel)使用將是非常不高效的。

  • (2)?嚴(yán)格來說,必須在托管堆上分配所有非 null 引用類型對象和所有裝箱值類型對象。

  • (3)?具體運(yùn)行時間視平臺而定。

  • (4)?注意,這與給定幀期間臨時分配的字節(jié)數(shù)同。性能分析器會顯示特定幀中分配的字節(jié)數(shù),不考慮在后續(xù)幀中是否重用了部分/全部已分配的內(nèi)存。

  • (5)?當(dāng)然,在返回數(shù)組后調(diào)整數(shù)組大小時是個例外。

了解托管堆的評論 (共 條)

分享到微博請遵守國家法律
武邑县| 崇阳县| 裕民县| 宜宾县| 宜丰县| 余干县| 渭南市| 呼图壁县| 贵德县| 华池县| 荃湾区| 云林县| 平谷区| 漠河县| 河曲县| 吉林省| 文安县| 潞城市| 龙门县| 扶绥县| 邹城市| 台前县| 开封市| 柏乡县| 玉树县| 江永县| 平阳县| 偏关县| 三原县| 祁阳县| 无锡市| 盐池县| 夏津县| 永济市| 铜川市| 樟树市| 大新县| 万山特区| 富阳市| 西吉县| 兴海县|