5

背景

之前的问题boost.pool我详细研究了boost.pool,现在我有一个补充问题来完成我的理解。

序幕

此参考说明了有关对象池模式的以下内容:

对象池模式是一种软件创建设计模式,它使用一组随时可用的已初始化对象,而不是按需分配和销毁它们。

据我所知,boost.pool(简化的)通过内存分配和管理主要基于 a 的大小来实现对象池模式element_type,并返回一个指向已分配对象的简单指针:

element_type * malloc();
void free(element_type * p);

一个简单的 boost 示例还表明,不需要显式free获取获取的元素:

X * const t = p.malloc();
... // Do something with t; don't take the time to free() it.

问题

我知道在销毁池对象时分配的内存将被安全释放,但是池如何知道客户端获取的内存块何时已释放回池中并且如果其接口交回直接指针则可重用to ,但仍然不需要element_type调用 to吗?free()即如果不能确定内存是否仍在使用中,提升池如何重新使用该内存?如果它不重用这个内存,这甚至被认为与维基参考解释的模式相同吗?

4

2 回答 2

8

如果无法确定内存是否仍在使用中,提升池如何重新使用该内存?

它不能。事实上,它不会重用该内存。它只保证在池被破坏时不会泄漏。

如果它不重用这个内存,这甚至被认为与维基参考解释的模式相同吗?

您链接的文章说: 在初始化类实例的成本很高的情况下,对象池可以显着提高性能

而来自Boost pool的介绍:Pools一般在小对象的分配和释放较多的情况下使用。

所以不,它们不是相同的模式。一种是重用构建成本高的对象(线程、opengl 资源等)。另一个是用来管理很多小对象,给你比标准分配器更多的控制权。

正如您所指出的,有两种使用池的方法:

  1. 作为分配器,在适当的时候调用 malloc()/free()。这是池分配器的基本用法,它有助于减少内存碎片
  2. 构造大量的临时对象,不要费心去删除它们。

第二种情况的示例:假设您有一个图类,其中每个节点使用指针存储其邻居。现在您必须制作图表的深层副本。您将分配一堆新节点并将数据从旧节点复制到新节点,但是现在您必须初始化邻居指针,因此您需要从旧指针到新指针的映射:

std::map<node*,node*> old_ptr_to_new_ptr;

这是一个很好的例子,池分配器很有用(我不会详细介绍如何将池分配器与 std 容器一起使用):许多将被一起删除的小对象(映射节点)。

于 2013-04-06T13:50:07.367 回答
7

boost pool 库提供了在分配相同类型的对象时更有效的STL 分配器std::allocator(而不是简单地使用newand delete)。这就是 Stroustrup 或 Alexandrescu 所说的小对象分配器

与任何自定义分配器类一样,它基本上使用四个独立的函数:分配、解除分配、构造和销毁。我认为它们的名称是不言自明的(除非您对分配与构造感到困惑)。要从池中获取新对象,您首先调用allocate(1)以获取指针,然后调用construct( ptr, value )该指针ptr以将其构造为value(或移动)的副本。当您想要删除该对象时,您会执行相反的操作。这些是所有 STL 容器在底层使用来分配-构造-销毁-释放其对象的机制。

您不应该相信您提到的维基百科文章(一般也不应该),它的措辞非常糟糕,使用了非常模糊和不准确的语言,并且对对象池模式的看法有些狭隘。顺便说一句,引用维基百科是毫无价值的,你不知道是谁写的,没有理由相信它,总是去源头。

wiki 中描述的模式(尤其是在源文章中)与提升池分配器试图实现的目标有很大不同。如 wiki 中所述,重点是重用对象而不真正销毁它们(例如,线程池是一个很好的例子,因为频繁创建和销毁线程会很昂贵,并且源文章对池化数据库服务感兴趣供应商出于类似原因)。在 boost pool 分配器中,重点是避免调用堆(freestore)来分配许多小对象,堆不能非常有效地执行这项任务并且会导致它变得碎片化。它可能应该被称为“小对象分配器”,以避免任何混淆。

池如何知道客户端获取的内存块何时已释放回池中并且如果其接口交回指向 element_type 的直接指针但仍不需要调用 free() 则可重用?

我不认为它可以。我相信故事是这样发展的。您可以选择只从池中分配一堆对象,而无需释放它们,这仍然是安全的,因为当您销毁池分配器时,它的所有内存都会被它刷新,包括您的所有对象留在池中徘徊。这就是他们所说的“不需要释放对象”的意思,只是如果您忘记从池中释放所有对象,您的应用程序不会在池分配器对象的生命周期之外泄漏内存。

但是,如果您不告诉池分配器释放您不再需要的对象(因此,可以重用),它将无法重用这些内存插槽,这是不可能的(鉴于分配器不' t 提供任何能够跟踪分配对象的特殊智能指针)。

如果无法确定内存是否仍在使用中,提升池如何重新使用该内存?

如果不能确定内存没有被使用,那么它就没有办法重新使用内存。任何会做出如此鲁莽的事情的代码,如“假设不再需要一个对象而不是确定”将是一段毫无价值的代码,因为它显然会具有未定义的行为,并且任何程序员都不可能使用它。

如果它不重用这个内存,这甚至被认为与维基参考解释的模式相同吗?

不,它没有实现 wiki 中解释的内容。您将不得不习惯术语有时会以不幸的方式发生冲突的事实。更常见的是将 boost pool 实现为“内存池”或“小对象分配器”。这是一种针对构造和复制相当便宜的小对象优化的结构。因为堆(freestore)是为更大的内存块量身定做的,并且在尝试为小对象寻找位置时往往表现不佳,因此将其用于该目的并不是一个好主意,并且可能导致堆碎片。因此,池分配器本质上用在分配许多相同类型的小对象时更有效的东西代替了堆。它不重用对象,但它可以重用已释放的内存,就像堆一样。std::vector)。在适当的时候使用小对象分配器还有许多其他性能优势。但是 boost pool 实现的实际上与 wiki 中描述的有很大不同。以下是实现者对池分配器的优点的描述:

使用 Pool 的好地方是在堆上可能分配许多(不连续的)小对象,或者相同大小的对象的分配和释放重复发生的情况。

于 2013-04-06T07:09:31.797 回答