在内存池中如何处理碎片化?

13
假设我有一个内存池对象,它的构造函数需要一个指向大块内存ptr和大小N的指针。如果我进行多个随机大小的分配和释放操作,可能会使内存达到这样一种状态:即使有很多空闲内存,我也无法连续地在内存中分配M字节的对象!同时,我也不能压缩内存,因为那会导致使用者的指针失效。在这种情况下,如何解决内存碎片问题?

你试图实现一个操作系统或者至少其中的一部分吗?内存池比普通分配更受欢迎的唯一原因是普通分配处理碎片问题。 - Daniel
5个回答

8
我想补充一下,因为没有人指出从你的描述中可以看出你正在实现一个标准的堆分配器(即我们每次调用malloc()或operator new时使用的内容)。
堆就是这样的对象,它去虚拟内存管理器那里请求大块内存(你所说的"池")。然后它有各种不同的算法来处理最有效的方式来分配各种大小的块和释放它们。此外,许多人多年来已经修改和优化了这些算法。长期以来,Windows带有一个称为低碎片堆(LFH)的选项,您需要手动启用它。从Vista开始,默认情况下,LFH用于所有堆。
堆不完美,如果不正确使用,它们肯定会影响性能。由于操作系统供应商不可能预测您将如何使用堆的每种情况,因此他们的堆管理器必须针对"平均"使用进行优化。但是,如果您的要求类似于常规堆的要求(即许多对象,不同大小...),则应该考虑只使用堆而不是重新发明它,因为您的实现很可能劣于操作系统已经为您提供的实现。
在内存分配方面,唯一可以通过不仅仅使用堆来提高性能的时间是放弃一些其他方面(分配开销,分配生命周期...),这对您的特定应用程序不重要。
例如,在我们的应用程序中,我们需要分配许多小于1KB的分配,但是这些分配仅在非常短的时间内使用(毫秒级别)。为了优化应用程序,我使用了Boost Pool库,但将其扩展,以便我的"分配器"实际上包含一组boost池对象,每个对象负责从16字节到1024字节(步长为4)分配一个特定大小。这提供了几乎免费(O(1)复杂度)的这些对象的分配/释放,但问题是a)内存使用量始终很大,即使我们没有单个对象分配,b)Boost Pool从不释放它使用的内存(至少在我们使用它的模式中),因此我们仅将其用于不保留很长时间的对象。
那么你的应用程序愿意放弃正常内存分配的哪些方面?

6
根据系统的不同,有几种方法可以实现。如果您分配块的大小为2的幂,那么就可以避免碎片化。还有其他一些方法可以解决这个问题,但是如果您达到了这种状态,就只能在OOM(内存不足)的情况下停止进程、阻塞直到可以分配内存或者返回NULL作为您的分配区域。另一种方法是传递数据的指针的指针(例如int **)。然后,您可以重新排列程序下方的内存(希望是线程安全的),并压缩分配,以便您可以分配新的块,并仍然保留旧的块中的数据(当系统达到这种状态时,这将成为一个沉重的负担,但很少会发生)。还有一些将内存“分组”的方法,例如只将1个页面专门用于512及以下的分配,另一个页面专门用于1024及以下的分配等等... 这样更容易做出使用哪个“分组”页面的决策,在最坏的情况下,您可以从下一个较高的“分组”页面拆分,或者从较低的“分组”页面合并,这降低了跨多个页面碎片化的可能性。

3
实现对象池来管理您经常分配的对象,将会极大地减少碎片化,而无需更改您的内存分配器。

1

了解您实际尝试做什么会很有帮助,因为有许多处理此问题的方法。
但是,第一个问题是:这是否真的发生了,还是理论上的担忧?

需要记住的一件事是,通常可用的虚拟内存地址空间比物理内存要多得多,因此即使物理内存是分段的,仍然有大量连续的虚拟内存。(当然,在下面的物理内存是不连续的,但您的代码看不到。)

我认为有时对内存碎片化存在不必要的恐惧,因此人们编写自定义内存分配器(或更糟糕的是,他们构思具有句柄和可移动内存以及压缩的方案)。我认为这些在实践中很少需要,并且有时放弃这些并回到使用malloc可以提高性能。


0
  • 将池编写为分配列表,可以根据需要进行扩展和销毁。这可以减少碎片。
  • 并/或者实现分配转移(或移动)支持,以便您可以压缩活动分配。对象/持有者可能需要协助您,因为池本身可能不知道如何转移类型。如果池与集合类型一起使用,则更容易实现压缩/转移。

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