2012-07-06 63 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的成本太高,因为您每次都会分配一个结构体,并将其按照堆栈中的值传递给所有方法。您需要仔细观察其使用情况是否正常,以及是否未以太高的速度分配。