如何在 Array.Resize 过程中避免 OutOfMemory 异常?

3

我的当前项目涉及植被模拟,能够渲染大量的树木实例模型,并在时间上进行生长和繁殖。

目前我在这行代码中不断遇到内存溢出异常:

if (treeInstances.Length <= currentIndex)
    Array.Resize(ref treeInstances, currentIndex + 500);

当模拟超出treeInstances数组的常规边界时,此代码将运行,并导致它分配一个新数组,其中包含额外的500个树插槽。

考虑到我在它失败时可以看到数组的大小(通常在3000和5000实例之间),以及TreeInstance结构的大小(20个浮点数),我确定我的问题不在于数组的原始大小。即使考虑到它在调整大小/8过程中必须被临时加倍(因为Array.Resize()会分配一个新数组),如果我的计算正确,那也还不到半MB。

因此,我认为我可能漏掉了一些东西。是否有某些原因,旧的数组可能不会被垃圾收集器删除?

更多细节:

  • TreeInstance是一个简单的结构体,包含每棵树的变换矩阵和颜色。
  • treeInstances是一个TreeInstance[]数组。它仅在此处直接使用,出现在上面的代码行中。
  • treeInstances还有一个属性TreeInstances,通过get;set;访问它。
  • TreeInstances用于设置每棵树的变换矩阵和颜色,随着其生长,并作为Draw例程的一部分馈送到实例化方法中。
  • 我不太熟悉实例化方法,但它执行各种功能,使用TreeInstances而不修改其内容(包括在DynamicVertexBuffer.SetData操作中将其用作源)。

1
为什么要手动分配数组?你考虑过使用List<T>吗?根据涉及的数组大小,+500分配可能不是增加数组最有效的方式。它们可能最终出现在大对象堆上并留下空洞。我的建议是尽可能使用List<T>。如果不行,请使用内存分析工具查看占用内存的内容。 - hawk
1
考虑到你的结构体中有20个浮点数,在数组中有3000-5000个元素,你肯定会进入大对象堆。一种策略是预先分配足够大的大小的数组。 - hawk
我使用自己的数组,因为实例化绘制方法需要将数组作为参数传递,直接分配数组似乎比每秒调用List<T>.ToArray() 60次更好。不过在这个阶段我还不能确定。 - Quasar
2个回答

1

C#旨在更好地处理小内存分配而不是大内存分配。当您执行Array.Resize时,您正在强制分配一个新的内存块,然后复制数据,然后使旧块无效。这是一种非常有效的算法,可以使堆碎片化 :-)

如果您知道起始时数组需要多大,请将数组设置为该大小。如果您不知道,建议使用List或类似的类。该类基于每个项进行分配。

我被纠正了,谢谢大家让我保持诚实。我太习惯于处理类而不是结构体了。我应该更加清醒。

如果TreeInstance更改为类,则List成为地址数组,TreeInstance将在较小的块中分配/分配。需要进行一些代码更改以新建所有TreeInstance。


错误;List<T>由单个数组支持。 - SLaks
3
你是否意识到 List<T> 使用数组作为其基本存储机制?它不是按每个项目分配空间,而是在用完空间时将当前底层数组复制到新数组中,新数组的大小是当前底层数组的两倍。 - Preston Guillot

0

看起来我找到了我的问题的答案,跟随Dweeberly对我的Array.Resize()系统的描述,它是“一种非常有效的算法,用于分段堆内存”。这是我的一个概念化问题:我不明白内存不足可能会导致内存不连续,而不是认为我因为垃圾回收没有捕获数组而达到某种限制。

Eric Lippert的这篇博客文章让我清醒了:

http://blogs.msdn.com/b/ericlippert/archive/2009/06/08/out-of-memory-does-not-refer-to-physical-memory.aspx

对于任何处理内存不足异常的人,或者对于游戏编程中的任何人来说,这都是值得一读的。

简而言之,在为32位Windows编译的程序中,通过Array.Resize()反复分配和删除大对象,会将您的地址空间碎片化成与您分配的对象一样大的“块”的空白空间。随后,尝试分配比这些空闲块中任何一个更大的对象将抛出内存不足异常,即使您的累积内存远远超过了它们的总和。

如上所述,适当的响应是避免重复调整数组大小:我只需要理解为什么。在我的情况下,这意味着重新编写实例化模型方法,仅绘制较大数组的子集,而不是整个数组。之后,只需在初始化期间分配比我所需更大得多的数组即可。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接