一个大型泳池或几个特定类型的泳池?

时间:2009-12-27 00:10:21

标签: c++ memory pool

我正在制作一款需要高性能的视频游戏,所以我正试图设置一个好的记忆策略或游戏的特定部分,即游戏“模型”,即游戏表现。我有一个包含整个游戏表示的对象,里面有不同的管理器,以保持表示符合游戏规则。每个游戏实体当前都是由特定类型的工厂生成的,所以我有几个工厂允许我按照自己的意愿隔离和更改这些实体的内存管理。

现在,我正在选择这两种选择之间:

  1. 为每种类型设置一个内存池:这将允许真正快速的分配/释放和最小的碎片,因为对象池已经知道分配的对象的大小。困扰我的一件事是让几个像这样的池分开,可能会让另一个解决方案更有效......
  2. 有一个大内存池由一个游戏代表的所有工厂共享 :(使用类似boost :: pool的东西,带有一些适配器功能)这样我就把所有游戏对象的内存分配到了一起并且可以为我已经知道总大小的游戏分配一个比特(并非总是如此)。我不确定这是一个比A更好的解决方案,因为池内可能存在碎片,因为在同一个池中会有不同大小的对象,但它看起来更容易进行内存分析和其他问题修复。
  3. 现在,我对A有了一些真实的经验,所以我对B没有经验,并希望对这些解决方案提出一些建议。 对于长寿命项目来说哪个解决方案似乎更好?为什么?(注意:在这种情况下,游泳池是非常必要的,因为游戏模型也用于游戏编辑,所以会有很多分配/解除分配小物件)。

    编辑澄清:如果(目前尚不清楚)我正在使用C ++

6 个答案:

答案 0 :(得分:8)

正确答案特定于您的问题域。但在我工作的问题域中,第一个通常是我们选择的域。

我做实时或接近实时的代码。主要是音频编辑和播放。在该代码中,我们通常不能在回放引擎中从堆中分配内存。大多数情况下malloc返回的速度足够快,但有时却没有。这有时很重要。

因此,我们的解决方案是为特定对象设置特定池,并将通用池用于其他所有对象。特定池具有预先分配的一定数量的元素,并且实现为链接列表(实际上是队列),因此分配和释放永远不会超过几个指针更新以及进入和离开关键部分的成本。

作为不寻常案件的后备;当有人需要从特殊池分配并且它是空的时 - 我们将分配一大块通用内存(几个对象)并将其添加到特殊池中。一旦分配成为特殊池的一部分,它将永远不会返回到通用池,直到应用程序退出或启动新项目。

对特殊池的初始大小和最大大小做出正确选择是调整应用程序的重要部分。

答案 1 :(得分:4)

您将遇到的一个问题是允许STL实现假设两个相同类型的分配器是等效的。这就是Boost.Pool只使用一个池的原因(从技术上讲,它为每种类型使用不同的池)。 I.E.,在一般情况下,您的分配器不允许有任何非静态成员。如果您正在制作视频游戏而且您知道您的STL实施没有此问题,那么请不要担心这一点 - 但list::splicestd::swap上可能仍存在一些问题容器

答案 2 :(得分:4)

对于初学者来说,对任何类型的视频游戏使用stl或boost都是不切实际的。你可以绝对肯定第二个你使用一个stl容器,你的内存是碎片化的,你的性能在厕所中是绝对的,至少与理想相比(因为大多数人的代码都在这个类别中,大多数人从来没有注意到,也无法真正比​​较还要别的吗)。我并不总是如此强烈地思考,但随着时间的推移,我甚至看到几行代码就像一个小小的gremlin,最终有一天会给你带来巨大的痛苦。

第一种方法是最常见的,如果你不想花费很多时间和精力来解决这个问题,可能是唯一可行的方法。第二种方式更好,因为它更通用,但可以根据您的确切需求量身定制,但这是很多工作,而不是轻易跳入。

答案 3 :(得分:2)

可能的解决方案之一是介于1.和2之间。

将池用于小对象:每个对象大小一个池。在这种情况下,您可以通过在数组中存储指针来轻松找到池。

此外,您可以为大型对象提供一个池。在这种情况下,碎片的可能性较小,而且时间开销并不是那么重要,因为大型对象不会经常分配和解除分配。

关于boost::pool的说明。在测试boost::pool的性能时,不仅要测试分配,还要测试释放。我经历过boost::poolboost::fast_pool解除分配时间可能非常大。我的案例包括在一个池中分配和取消分配不同大小的小对象

答案 4 :(得分:0)

我对您正在考虑的内存管理器没有具体经验,但这里有一些可能有用的一般指导原则:

  • 如果你不期望内存不足,那么选项1将是最好的,因为你说它很快(比2?更快),并且拥有单独的池将更容易发现分配/释放/缓冲区问题(假设池管理器具有良好的错误检测功能)。
  • 如果内存可能成为一个问题(因为在游戏中,与目标平台的常用内存相比会占用大量内存),拥有一个大型池将提供更高效的内存使用。此外,如果您无法准确预测每个池的平均和最大内存需求,那么这是一个更好的选择,除非内存管理器可以动态增长内存池(并且理想情况下也可以动态地从池中释放块)。我看到2的唯一缺点是它可能更慢(是这样吗?),并且内存管理中的错误可能更难检测。

通过使用多个池进行开发,您可以充分利用这两个方面(假设速度相似),但是使用单个池进行最终测试和生产发布。这样,您可以在开发期间发现分配/管理问题,但仍然可以从可能更高效的单一池中受益。

答案 5 :(得分:0)

实际上,我会选择2.我可以从linux内核给你一个例子。在内核中,dentry(目录条目)和inode对象应该在内存中缓存更长时间,以便更好地响应用户。由于inode对象依赖于文件系统,因此每个文件系统都将创建自己的对象池。如果对象相似,您可以做的另一件事是抽象出对象并在一个抽象对象中保留公共属性,并使用容器存储对象特定信息。请参阅以下代码以获取完整的想法。

http://lxr.linux.no/linux+v2.6.32/fs/ext2/super.c#L149