视频游戏中的纹理流式传输的C++内存管理

21

这是一个很“难”的问题。我在网上没找到有趣的东西。

我正在为我的公司开发内存管理模块。我们为下一代游戏机(Xbox 360、PS3和PC…我们认为PC是游戏机!)开发游戏。

为了处理无法全部加载到主控制台内存中的大型游戏世界(暂不考虑PC),在未来的游戏中我们需要进行纹理流处理。

我们将在开始时流式传输纹理的高分辨率mipmap(大约占据世界数据大小的70%)。也许在未来我们还需要流式传输几何图形、更小的mipmap、音频等。

我正在为此问题开发一个内存管理器,重点针对X360(因为在PS3上我们可以使用主机内存和相关自动碎片整理GMM分配器)。

我面临的问题是:我们已经决定为纹理流式传输保留一个特定的内存区域(例如64兆字节),并且我们希望在该区域处理所有分配和释放。我们已经在应用程序开始时分配了该区域,并且物理上保证是连续的(不仅仅是虚拟的,因为我们需要在那里存储纹理)。

我实现了一个使用句柄而不是指针的自动碎片整理分配器。时间不是问题,问题是内存碎片。在游戏中,我们不断地加载和卸载流式传输目标,因此我们希望使用我们缓冲区的最大容量(64兆字节)。

通过这个分配器,我们可以使用所有已分配的空间,但碎片整理例程的运行时间过长(有时需要60毫秒,超过了一帧!),尽管算法并不太糟糕…只是有太多不可避免的memcpy!

我正在寻找解决这个问题的方法。我希望至少能找到一篇好的论文、或者一篇事后总结、或者有人曾经面临过和我同样的问题。

现在我正在选择两种策略: 1)将碎片整理例程移动到专用线程上(对于拥有6个硬件线程的X360来说很好,对于只有一个硬件线程的PS3来说很差…别告诉我要使用SPU!)会有所有多线程锁定区域、访问正在移动的区域等多线程问题。2) 找到一种“增量”解决碎片整理问题的方法:我们可以为每个帧分配一个时间预算(例如最多1毫秒)进行碎片整理,内存管理器将在每个帧中尽力完成预算内的任务。

有人能分享他的经验吗?

5个回答

13

最近我对内存管理进行了大量研究,而这篇文章是我在网络上找到的最有用和信息最丰富的文章。

http://www.ibm.com/developerworks/linux/library/l-memory/

根据那篇论文,你将获得最佳和最快的结果是将你的 64 MB 划分成大小相等的块。块的大小将取决于你的对象大小。并一次性分配或释放一个完整的块。

  1. 比增量垃圾收集更快。
  2. 更简单。
  3. 并且以某种程度解决了“过多碎片化”的问题。

阅读它,你会发现实际上有各种可能的解决方案,每个方案都有其优点和缺点。


1
作为一名游戏开发者,我支持这个想法。我们很幸运游戏纹理默认大小适中,所以它应该可以正常工作,但你也可以考虑使用几个桶尺寸而不是单个桶。现在另一个要考虑的优化是创建一个算法,将要在同一渲染通道中使用的纹理放在一起,但这不会有非常大的差异,如果你真的需要的话,可能只是记住并进行优化,这取决于游戏和渲染。 - Robert Gould
这是我迄今为止找到的最好的链接之一。非常感谢。 - pigiuz
1
工作链接:https://web.archive.org/web/20090228181848/http://www.ibm.com/developerworks/linux/library/l-memory/ - Dan Bechard

5
为什么不使用多个内存区域来存储流式纹理,并按纹理大小进行池化呢?
Insomniac在PS3上实现的纹理流式处理方面有一篇论文,这可能会有所帮助:链接
对于最小化碎片的通用分配策略,也许Doug Lea可以提供帮助。
但从您的问题描述中,我认为您正在过度思考,强烈建议采用池化方法。(此外,在写入组合内存时运行碎片整理程序似乎并不特别安全或有趣。)

两个链接都失效了。这是为什么只提供链接答案毫无用处的完美例子。:( - Dan Bechard
工作中的Doug Lea链接:https://web.archive.org/web/20090308095452/http://gee.cs.oswego.edu/dl/html/malloc.html 我找不到失眠症论文。 - Dan Bechard

2

我们几乎有您描述的完全相同的系统,只是我们分配固定大小的插槽 - 256x256、512x512、1024x1024和2048x2048纹理,每种格式各两个(DXT1和DXT5)- 正是为了避免内存管理。


你们没有矩形纹理吗?我们也有512x1024、256x512和256x1024的纹理(但我们避免使用2048的纹理)。这将导致很多“固定尺寸”... - ugasoft

2
由于您正在使用句柄,因此您有很大的自由度来移动内存。我认为使用单独的线程可能不是最好的(最安全或最快)方法 - 我猜您最好使用一种增量复制分配器,在每个malloc()free()上,您可以压缩(向前或向后复制)一些已分配块,所复制的字节数会消耗“预算”,该预算定期重置为其初始值(例如在每个屏幕刷新时)。 (当然只有整个块才能被复制。)
这个想法是,复制给定数量的字节需要相当可预测的时间,因此您可以估计在每个屏幕刷新中安全执行多少字节的复制,并限制自己进行复制。 如果预算中有足够的时间,则对malloc()free()的调用将完全碎片化内存,否则它将在给定的时间约束下尽可能地碎片化内存。
这里有一些我没有解决的问题 - 例如如何压缩内存。标准的非增量复制分配器可以从前面开始分配,然后将所有内容复制到后面(在内存耗尽时释放前面的内存),但您在这里没有这种自由。您可能需要一些启发式算法来决定是否将块向前或向后移动。重要的是要避免振荡(同一块在连续调用malloc()free()时向前然后向后移动)。

1
我建议采用增量方法。每一帧找到一个连续的已使用内存块,它两侧有空闲空间,并将其向适合的方向移动。或者您可以只将所有块向一个方向移动,找到一个间隙和最适合它的已使用块,并将其移动。在360上,最好使用线程进行移动,而在PS3上,最好使用GPU来为您移动数据。

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