2012-07-06 58 views
2

我的遊戲已經到了產生過多垃圾並導致GC時間長的地步。我一直在四處走動,減少了大量垃圾產生,但有一個地方太頻繁地分配大量內存,我一直在堅持如何解決這個問題。如何減少這種情況下的垃圾產生

我的遊戲是一個世界級的世界,當你走路時會產生新的區域。我有一個大的,可變大小的數組,用於創建一個用於存儲地形頂點數據的新區域。數組填充數據後,將其傳遞給slimdx DataStream,以便用於渲染。

問題是,這是一個可變大小的數組,它需要傳遞給slimdx,它調用GCHandle.Alloc。由於它的大小可變,因此可能需要重新調整大小才能重新使用它。我也不能只爲每個區域分配一個最大大小的數組,因爲它需要不可能的大量內存。由於GCHandle與slimdx的業務,我無法使用列表。

到目前爲止,只有當需要使數組變大時才調整數組大小似乎是我唯一合理的選擇,但它可能無法很好地實現,並且可能會很難實現。我需要分別跟蹤數組的實際大小,並使用不安全的代碼來獲取指向數組的指針並將其傳遞給slimdx。它最終最終可能會使用如此大量的內存,我偶爾會將所有陣列的大小降低到所需的最小值。

我很猶豫跳到這個解決方案,想知道是否有人看到任何更好的解決方案。

+0

你確定它只是數組創建嗎?數組是否包含類對象或結構體?如果它確實包含類,則可能是您創建了一個大而複雜的對象圖,它將花費更長的時間來檢查垃圾。 – 2012-07-07 04:39:56

+0

它是一個結構數組。該結構包含3個結構體(Vector3)和2個浮點數。 CLR分析器分配圖將此結構列爲最大的違規者,數量接近300MB(25%)。 – Telanor 2012-07-07 05:58:54

回答

1

我建議與slimdx庫進行更緊密的集成。它是開源的,所以你可以深入挖掘並找到渲染所需的關鍵路徑。然後,您可以通過使用DMA風格的內存共享方法來更緊密地整合。

+0

嗯,slimdx可以讓你用3種方式創建一個DataStream:傳遞它一個大小,它分配一個後備存儲,傳遞一個數組並且使用它,或者傳遞一個指針。由於我已經在使用數組方法,是不是已經有內存共享? – Telanor 2012-07-06 23:10:48

+0

仔細研究一下,確保庫不會做一些奇怪的事情,比如只要你傳遞一半數組到另一個數組就行。然後取消分配該半角temp數組並導致大量垃圾。 – 2012-07-07 13:47:56

+0

此外,地形頂點地圖佔用300MB似乎過多。如果您僅隔離關鍵渲染關鍵路徑並移除對結構其他部分的任何檢查,然後從結構中刪除這些關鍵路徑,那麼您可以充分利用稀疏性。 – 2012-07-07 13:50:13

0

我同情你的問題與舊的庫,slimdx,這可能不符合.NET。我已經處理了這種情況。

建議:

  1. 使用更高效的性能泛型列表或數組如ArrayList。它跟蹤數組的大小,所以你不必這樣做。一次分配列表,大塊,例如每次100個元素。
  2. 使用C++ .NET並利用不安全的數組或像ArrayList這樣的.NET類。
  3. 更新:使用虛擬內存的想法。將一些數據保存到XML文件或SQL數據庫,從而減少大量內存。

我意識到這是賭博的任何一種方式。

+0

我無法使用自動調整大小的通用.net類。在調整大小後,數據將被複制到新的內存地址,而我不知道,slimdx仍然會指向舊的位置。我看不出C++ .net會如何幫助解決這個問題。 – Telanor 2012-07-07 02:28:08

+0

使用C++,您的代碼可以使用不安全的類型和指針,並且可以將地址傳遞給slimdx函數。但你仍然必須自己管理記憶。 – 2012-07-07 02:44:31

+0

使用虛擬內存的想法,正如我添加到上面的建議列表。我知道這增加了代碼,但它不是軟件重新設計。 – 2012-07-07 02:48:09

1

由於SlimDX是開源的,而且速度太慢,因此需要改變開源以滿足您的性能需求。我在這裏看到的是,你想保留一個更大的數組,但只交給SlimDX實際使用的區域,以防止爲這個潛在巨大的數組增加內存分配。

在.NET Framework中有一個名爲ArraySegment的類型,它完全是爲此目的而創建的。

// Taken from MSDN 
// Create and initialize a new string array. 
String[] myArr = { "The", "quick", "brown", "fox", "jumps", "over", "the", 
        "lazy", "dog" }; 

// Define an array segment that contains the middle five values of the array. 
ArraySegment<String> myArrSegMid = new ArraySegment<String>(myArr, 2, 5); 


public static void PrintIndexAndValues(ArraySegment<String> arrSeg) 
{ 
    for (int i = arrSeg.Offset; i < (arrSeg.Offset + arrSeg.Count); i++) 
    { 
     Console.WriteLine(" [{0}] : {1}", i, arrSeg.Array[i]); 
    } 
    Console.WriteLine(); 
} 

不過我發現ArraySegment的使用有些奇怪,因爲我總是要使用的偏移量和剛剛的行爲不是一般的數組的索引。相反,您可以提取自己的結構,它允許基於零的索引使用,但使用起來更容易,但代價是每個基於索引的訪問都會花費您的成本並添加基準偏移量。但是,如果使用模式主要是惡意的,那麼它並不重要。

我有些情況,ArraySegment的成本太高,因爲您每次都會分配一個結構體,並將其按照堆棧中的值傳遞給所有方法。您需要仔細觀察其使用情況是否正常,以及是否未以太高的速度分配。