处理内存池中的碎片?

时间:2011-10-22 04:47:12

标签: c++ memory pool fragmentation

假设我有一个带有构造函数的内存池对象,该构造函数接受指向大块内存ptr和大小N的指针。如果我做了许多随机分配和各种大小的解除分配,我可以获得内存处于这样一种状态:即使可能有很多空闲,也无法在内存中连续分配M字节对象!与此同时,我无法压缩内存,因为这会导致消费者的悬空指针。在这种情况下如何解决碎片?

5 个答案:

答案 0 :(得分:8)

我想加上我的2美分只是因为没有人指出你的描述听起来好像你正在实现一个标准的堆分配器(即我们所有人在每次调用malloc()或operator new时都已经使用过)。

堆就是这样一个对象,它转到虚拟内存管理器并请求大块内存(你称之为“池”)。然后它有各种不同的算法来处理分配各种大小的块并释放它们的最有效方法。此外,许多人多年来一直在修改和优化这些算法。很长一段时间,Windows都提供了一个称为低碎片堆(LFH)的选项,您以前必须手动启用它。从Vista开始LFH默认用于所有堆。

堆不完美,如果使用不当,肯定会使性能陷入困境。由于操作系统供应商无法预测您将使用堆的每个场景,因此他们的堆管理器必须针对“平均”使用进行优化。但是如果你的需求类似于常规堆的需求(即许多对象,不同大小......),你应该考虑只使用堆而不是重新发明它,因为你的实现可能性不如操作系统已经为你提供了。

通过内存分配,您不仅可以通过不仅仅使用堆来获得性能,还可以放弃一些其他方面(分配开销,分配生命周期......),这对您的特定应用程序并不重要。

例如,在我们的应用程序中,我们要求许多分配小于1KB,但这些分配仅用于非常短的时间段(毫秒)。为了优化应用程序,我使用了Boost Pool库但扩展了它,以便我的“allocator”实际上包含一组boost池对象,每个对象负责分配一个特定大小,从16个字节到1024个(步长为4)。这提供了几乎免费(O(1)复杂性)分配/免除这些对象,但问题是:a)内存使用总是很大,即使我们没有分配单个对象也不会失败,b)Boost Pool never释放它使用的内存(至少在我们使用它的模式中),所以我们只将它用于那些不会长时间停留的对象。

您希望在应用中放弃正常内存分配的哪个方面?

答案 1 :(得分:6)

根据系统的不同,有几种方法可以做到。

首先尝试避免碎片,如果你以2的幂分配块,你就不太可能造成这种碎片。还有其他几种方法,但是如果你达到了这个状态,那么你就是OOM,因为除了杀死要求内存的进程,阻塞直到你可以分配内存之外,没有其他方法可以处理它,或者返回NULL作为您的分配区域。

另一种方法是将指针传递给数据指针(例如:int **)。然后你可以重新安排程序下面的内存(我希望线程安全)并压缩分配,这样你就可以分配新的块并仍然保留旧块的数据(一旦系统达到这种状态,虽然这会成为一个沉重的开销,但很少应该很少完成)。

还有“binning”内存的方法,以便你有连续的页面,例如专用1页只分配512或更少,另一个1024和更少等等...这使得更容易决定哪些要使用的bin,在最坏的情况下,您从下一个最高的bin分割或从较低的bin合并,这样可以减少在多个页面之间分割的可能性。

答案 2 :(得分:3)

为您经常分配的对象实现object pools将大大降低碎片,而无需更改内存分配器。

答案 3 :(得分:1)

更准确地了解您实际上要做的事情将会有所帮助,因为有很多方法可以解决这个问题。
但是,第一个问题是:这实际发生了,还是理论上的问题?

要记住的一件事是,您通常拥有比物理内存更多的虚拟内存地址空间,因此即使物理内存碎片化,仍然有大量连续的虚拟内存。 (当然,物理内存在下面是不连续的,但你的代码没有看到它。)

我认为有时候对内存碎片有无根据的恐惧,因此人们会编写一个自定义内存分配器(或者更糟糕的是,他们用一个句柄和可移动的内存和压缩来编写一个方案)。我认为这些在实践中很少需要,有时可以提高性能,然后再使用malloc。

答案 4 :(得分:0)

  • 将池编写为分配列表,然后根据需要进行扩展和销毁。这可以减少碎片。
  • 和/或实现分配传输(或移动)支持,以便您可以压缩活动分配。对象/持有者可能需要帮助您,因为池可能不一定知道如何传输类型本身。如果池与集合类型一起使用,那么完成压缩/传输就容易得多。