9

我正在开发一款需要高性能的视频游戏,所以我正在尝试设置一个好的内存策略或游戏的特定部分,即游戏“模型”的部分,游戏表示。

我有一个包含整个游戏表示的对象,里面有不同的管理器,以保持表示一致,遵循游戏规则。每个游戏实体当前都是由特定类型的工厂生成的,所以我有几个工厂可以让我根据需要隔离和更改这些实体的内存管理。

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

  1. 每种类型都有一个内存池:这将允许真正快速的分配/释放和最小的碎片,因为对象池已经知道分配对象的大小。困扰我的一件事是有几个这样的独立池,也许会使其他解决方案更有效......
  2. 让一个游戏表示的所有工厂共享一个大内存池:(使用带有一些适配器功能的 boost::pool 之类的东西)这样我就可以将所有游戏对象内存分配在一起,并且可以为一个游戏分配一个位我已经知道总大小(并非总是如此)。我不确定它是否比 A 更好,因为池内可能存在碎片,因为在同一个池中会有不同大小的对象,但对于内存分析和其他问题修复来说,它看起来更容易。

现在,我在 A 方面有一些现实世界的经验,所以我对 B 没有经验,并且想就这些解决方案获得一些建议,用于长期项目。哪种解决方案似乎更适合长期项目,为什么?(注意:在这种情况下确实需要一个池,因为游戏模型也用于游戏编辑,因此会有很多小对象的分配/释放)。

为澄清而编辑:我正在使用 C++ if(尚不清楚)

4

6 回答 6

10

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

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

所以我们的解决方案是为某些对象设置特定的池,而对其他所有对象使用通用池。特定的池预先分配了一定数量的元素,并以链表(实际上是队列)的形式实现,因此分配和释放永远不会超过几次指针更新和进入和离开临界区的成本。

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

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

于 2009-12-27T01:01:39.833 回答
4

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

于 2009-12-27T01:09:40.910 回答
4

对于初学者来说,将 stl 或 boost 用于任何类型的视频游戏都是不切实际的。您可以绝对确定,在您使用一个 stl 容器的那一刻,您的记忆是碎片化的,至少与理想相比,您在厕所里的表现是无可救药的(因为大多数人的代码都属于这一类,所以大多数人从未注意到并且无法真正比​​较还要别的吗)。我并不总是这么强烈地想,但随着时间的推移,我已经看到即使是几行代码也像一个小精灵,最终有一天会给你带来巨大的痛苦。

第一种方法是最常见的,作为一个做过这两种方法的人,如果你不想在这个问题上花费更多的时间和精力,这可能是唯一可行的方法,而这对你来说可能不值得。第二种方法更好,因为它更通用,但可以根据您的确切需求进行定制,但它需要做很多工作,而不是轻易投入。

于 2009-12-27T10:05:38.953 回答
2

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

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

此外,您还可以拥有一个大型对象池。在这种情况下,碎片不太可能发生,时间开销也不是那么重要,因为大对象不会非常频繁地分配和释放。

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

于 2009-12-27T00:32:35.667 回答
0

我对您正在考虑的内存管理器没有具体经验,但这里有一些可能会有所帮助的一般准则:

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

通过使用多个池进行开发,但使用单个池进行最终测试和生产发布,您可能会获得两全其美(假设速度相似)。这样,您可以在开发过程中发现分配/管理问题,但仍然可以从潜在的更高效的单一池中受益。

于 2009-12-27T00:27:21.890 回答
0

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

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

于 2009-12-27T03:31:30.327 回答