关于内存池的问题

时间:2011-07-19 13:31:33

标签: c++ memory-management fragmentation memory-pool

我需要对这个概念进行一些澄清。在内存池上实现。

在wiki上的memory pool,它说

  

也称为固定大小块分配,...,   因为这些实现因变量而受到碎片的影响   块大小,在实时系统中使用它们是不可能的   由于表现。

“可变块大小如何导致碎片化”如何发生?固定大小的分配如何解决这个问题?这个维基描述对我来说听起来有点误导。我认为通过固定大小的分配或由可变大小引起的碎片不可避免。在内存池上下文中,特定应用程序的特定设计内存分配器可以避免碎片,或者通过限制使用预期的内存块来减少碎片。

同样通过几个实现示例,例如Code Sample 1Code Sample 2,在我看来,使用内存池,开发人员必须非常了解数据类型,然后剪切,拆分或将数据组织到链接的内存块(如果数据接近链接列表)或分层链接的块(如果数据更加层次化,如文件)。此外,开发人员似乎必须事先预测他需要多少内存。

好吧,我可以想象这适用于一系列原始数据。 C ++非原始数据类怎么样,内存模型不是很明显?即使对于原始数据,开发人员是否应该考虑数据类型对齐?

是否有适合C和C ++的内存池库?

感谢您的任何评论!

5 个答案:

答案 0 :(得分:12)

可变块大小确实会导致碎片化。看看我附上的图片:enter image description here

图像(from here)显示A,B和C分配内存块,可变大小的块的情况。

在某些时候,B释放了所有的内存块,突然间你就会出现碎片。例如,如果C需要分配大量内存,但仍然适合可用内存,则无法做到,因为可用内存分为两个块。

现在,如果你考虑每个内存块大小相同的情况,显然不会出现这种情况。

当然,内存池有其自身的缺点,正如您自己所指出的那样。所以你不应该认为内存池是一个神奇的魔杖。它有成本,在特定情况下支付它是有意义的(即内存有限的嵌入式系统,实时约束等)。

至于哪个内存池在C ++中是好的,我会说它取决于。我在操作系统提供的VxWorks下使用了一个;从某种意义上说,一个好的内存池在与OS紧密集成时是有效的。实际上,我猜每个RTOS都提供了内存池的实现。

如果您正在寻找通用内存池实现,请查看this

编辑:

从您最后的评论中,在我看来,您可能认为内存池是解决碎片问题的“解决方案”。不幸的是,这种情况并非如此。如果你愿意,碎片是记忆层面熵的表现,即它是不可避免的。另一方面,内存池是一种管理内存的方式,可以有效地减少碎片的影响(正如我所说,并且正如维基百科所提到的,主要是在特定系统上,如实时系统)。这需要付出代价,因为内存池的效率低于“普通”内存分配技术,因为您拥有最小的块大小。换句话说,熵在伪装下重新出现。

此外,这是影响内存池系统效率的许多参数,例如块大小,块分配策略,或者您是否只有一个内存池,或者您有多个具有不同块大小,不同生命周期或不同内存池的内存池政策。

内存管理实际上是一个复杂的问题,内存池只是一种技术,与其他技术一样,与其他技术相比可以改进,并且确定了自己的成本。

答案 1 :(得分:3)

在您总是分配固定大小的块的情况下,您要么有足够的空间再容纳一个块,要么您没有。如果有,则块适合可用空间,因为所有空闲或已用空间的大小相同。碎片不是问题。

在具有可变大小块的方案中,您最终可以使用具有不同大小的多个单独的空闲块。对于大小小于可用总存储空间的块的请求可能无法满足,因为没有一个足够大的连续块。例如,假设您最终得到两个单独的2KB空闲块,并且需要满足3KB的请求。即使有足够的可用内存,这些块都不足以提供它。

答案 2 :(得分:2)

固定大小和可变大小的内存池都具有碎片功能,即在使用过的碎片之间会有一些空闲的内存块。

对于可变大小,这可能会导致问题,因为可能没有足够大的空闲块来满足特定请求的大小。

另一方面,对于固定大小的池,这不是问题,因为只能请求预定义大小的部分。如果有可用空间,则保证足够大(一倍)一部分。

答案 3 :(得分:1)

如果您使用硬实时系统,您可能需要提前知道可以在允许的最长时间内分配内存。这可以通过固定大小的内存池“解决”。

我曾经在军事系统上工作,我们必须计算系统可能使用的每种尺寸的最大可能内存块数。然后将这些数字添加到总计中,系统配置了该内存量。

疯狂昂贵,但为辩护工作。


如果您有多个固定大小的池,即使其他池中有足够的空间,您也可以获得池中没有块的二级碎片。你是如何分享的?

答案 4 :(得分:1)

使用内存池,操作可能会像这样工作:

  1. 存储一个全局变量,该变量是可用对象列表(最初为空)。
  2. 要获取新对象,请尝试从全局可用列表中返回一个对象。如果没有,则调用operator new以在堆上分配新对象。分配速度非常快,这对于目前可能在内存分配上花费大量CPU时间的某些应用程序非常重要。
  3. 要释放对象,只需将其添加到可用对象的全局列表中即可。您可以对全局列表中允许的项目数设置上限;如果达到上限,那么将释放该对象而不是返回到列表。该帽可防止出现大量内存泄漏。
  4. 请注意,对于相同大小的单个数据类型,始终执行此操作;它不适用于较大的那些,然后你可能需要像往常一样使用堆。

    实施起来非常容易;我们在申请中使用此策略。这会在程序开始时产生大量内存分配,但不会再发生内存释放/分配,从而导致大量开销。