boost.pool如何实现分配内存的重用?

jwa*_*alk 5 c++ boost-pool

背景

先前的问题有关boost.pool促使我详细调查boost.pool,现在我有一个补充问题,最后确定我的理解.

序幕

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

对象池模式是一种软件创建设计模式,它使用一组准备好使用的初始化对象,而不是按需分配和销毁它们.

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

element_type * malloc();
void free(element_type * p);
Run Code Online (Sandbox Code Playgroud)

一个简单的提升示例还表明,没有必要明确free获取的元素:

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

我知道在破坏池对象时可以安全地释放分配的内存,但是池如何知道客户端获取的内存块何时被释放回池中并且如果其接口提交直接指针则可以重用到element_type,但free()仍然不需要打电话?也就是说,如果无法确定内存是否仍在使用中,那么增强池如何重新使用此内存?如果它不重用这个内存,这甚至被认为是与wiki引用解释的模式相同的模式?

sba*_*bbi 7

如果无法确定内存是否仍在使用中,那么boost池如何重用此内存?

它不能.实际上它不会重用那个内存.它只能保证在池被破坏时你没有泄漏.

如果它不重用这个内存,这甚至被认为是与wiki引用解释的模式相同的模式?

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

Boost池引入时:通常在小对象的大量分配和释放时使用池.

所以不,它们不是同一种模式.一种意图是重新使用构造昂贵的对象(线程,opengl资源等).另一个是管理很多小对象,比标准分配器给你更多的控制.

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

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

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

std::map<node*,node*> old_ptr_to_new_ptr;
Run Code Online (Sandbox Code Playgroud)

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


Mik*_*son 6

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

作为任何自定义分配器类,它基本上与四个独立的函数一起工作:allocate,deallocate,construct和destroy.我认为他们的名字是不言自明的(除非你对分配与建构混淆).要从池中获取新对象,首先调用allocate(1)以获取指针,然后调用construct( ptr, value )该指针ptr以将其构造为value(或移动)的副本.如果要删除该对象,则执行相反的操作.这些是所有STL容器使用引擎来分配 - 构造 - 销毁 - 释放其对象的机制.

你不应该相信你提到的维基百科文章(而且一般也不是这样),它的措辞非常差,使用非常模糊和不准确的语言,并且对对象池模式采取一些狭隘的观点.顺便说一句,引用维基百科是没有价值的,你不是谁写的,没有理由相信它,总是去源头.

wiki中描述的模式(特别是在源文章中)与boost池分配器尝试完成的目标有很大不同.正如wiki中所描述的那样,重点在于重用对象而不会真正破坏它们(例如,线程池是一个很好的例子,因为经常创建和销毁线程会很昂贵,并且源文章对汇集数据库服务感兴趣提供者出于类似的原因).在boost pool allocators中,重点是避免调用堆(freestore)来分配许多小对象,这是一个堆无法高效执行的任务,并会导致它变得碎片化.它应该被称为"小对象分配器",以避免任何混淆.

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

我不认为它可以.我相信这个故事是这样的.您可以选择从池中分配一堆对象,而无需取消分配它们,并且这仍然是安全的,因为当您销毁池分配器时,其所有内存都会被刷新,包括您的所有对象留在池中徘徊.这就是他们所说的"它不需要释放对象",只是如果你忘记释放池中的所有对象,你的应用程序不会泄漏超出池分配器对象生命周期的内存.

但是,如果你没有告诉pool-allocator释放你不再需要的对象(因此可以重用),那么它将无法重用那些内存插槽,这是不可能的(假设分配器没有'提供任何能够跟踪分配对象的特殊智能指针.

如果无法确定内存是否仍在使用中,那么boost池如何重用此内存?

如果无法确定内存是否仍在使用中,则无法重新使用内存.任何一段代码都会做出如此鲁莽的事情,以至于"假定不再需要一个对象而不确定"将是一段毫无价值的代码,因为它显然会有未定义的行为,并且程序员也无法使用它.

如果它不重用这个内存,这甚至被认为是与wiki引用解释的模式相同的模式?

不,它没有实现wiki中解释的内容.你必须习惯这样一个事实,即术语有时会以不幸的方式发生冲突.更常见的是将boost池实现为"内存池"或"小对象分配器".这是一种针对稍微小的对象进行优化的结构,其构造和复制相当便宜.因为堆(freestore)是为更大的内存块量身定制的,并且在尝试为小对象找到一个位置时往往处理不好,因此将其用于此目的并不是一个好主意,并且可能导致堆碎片.因此,池分配器实质上用一些在分配相同类型的许多小对象时更有效的东西替换堆.它不会重用对象,但它可以重用已释放的内存,就像堆一样.它通常将其内存(它分配)从堆中分配为一个大的连续块(​​例如,with std::vector).在适当的时候使用小对象分配器还有许多其他性能优势.但是,boost池实现的实际上与wiki中描述的完全不同.以下是实施者对池分配器有用的描述:

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