内存池背后的常见实现细节是什么?

Oth*_*oun 17 c++ memory-management memory-pool

我试图理解使用内存池进行内存管理,但我找不到太多关于它的内容,尽管它似乎是一种非常常见的机制.

我所知道的就是每个维基百科的 "内存池,也称为固定大小的块分配" ,我可以使用这些块为我的对象分配内存.

有关内存池的标准规范吗?

我想知道它在堆上是如何工作的,它是如何实现的,以及如何使用它?

这个关于C++ 11内存池设计模式的问题,我读过:

如果你还没有,请熟悉Boost.Pool.从Boost文档中:

什么是游泳池?

池分配是一种非常快速的内存分配方案,但其使用受到限制.有关池分配更多的信息(也称为简单的分隔式,看概念概念和简单的分隔式.

我可以理解他的意思,但这并不能帮助我理解如何使用它们以及内存池如何帮助我的应用程序,如何实际使用它们.

将会欣赏一个显示如何使用内存池的简单示例.

Dra*_*rgy 25

任何类型的"池"实际上只是您事先获得/初始化的资源,因此它们已经准备就绪,而不是随着每个客户端请求即时分配.当客户端完成使用它们时,资源将返回池而不是被销毁.

内存池基本上只是你事先分配的内存(通常是大块).例如,您可以提前分配4千字节的内存.当客户端请求64字节的内存时,您只需将它们指向该内存池中未使用的空间,以便它们读取和写入任何所需内容.客户端完成后,您可以将该部分内存标记为再次未使用.

作为一个基本的例子,它不会因为对齐,安全或将未使用(释放)的内存返回池而烦恼:

class MemoryPool
{
public:
    MemoryPool(): ptr(mem) 
    {
    }

    void* allocate(int mem_size)
    {
        assert((ptr + mem_size) <= (mem + sizeof mem) && "Pool exhausted!");
        void* mem = ptr;
        ptr += mem_size;
        return mem;
    }

private:
    MemoryPool(const MemoryPool&);
    MemoryPool& operator=(const MemoryPool&);   
    char mem[4096];
    char* ptr;
};

...
{
    MemoryPool pool;

    // Allocate an instance of `Foo` into a chunk returned by the memory pool.
    Foo* foo = new(pool.allocate(sizeof(Foo))) Foo;
    ...
    // Invoke the dtor manually since we used placement new.
    foo->~Foo();
}
Run Code Online (Sandbox Code Playgroud)

这实际上只是从堆栈中汇集内存.更高级的实现可能会将块链接在一起并进行一些分支以查看块是否已满以避免内存不足,处理作为联合的固定大小的块(空闲时列出节点,使用时为客户端提供内存),以及它肯定需要处理对齐(最简单的方法是最大限度地对齐内存块并为每个块添加填充以对齐后续的块).

更奇怪的是伙伴分配器,平板,应用拟合算法的等等.实现分配器与数据结构没有太大区别,但是你得到了原始位和字节的深度,必须考虑像对齐这样的东西,并且可以' t shuffle contents around(不能使现有的指向正在使用的内存的指针无效).就像数据结构一样,并没有真正的金标准说"你要做到这一点".它们有很多种,每种都有自己的优点和缺点,但有一些特别流行的内存分配算法.

我实际上会向许多C和C++开发人员推荐实现分配器,以便与内存管理工作方式更好地协调一致.它可以使您更加关注所请求的内存如何使用它们连接到数据结构,并且在不使用任何新数据结构的情况下打开了一个全新的优化机会之门.它还可以使链接列表等通常效率不高的数据结构更加有用,并减少诱惑,使不透明/抽象类型不那么透明,以避免堆开销.然而,可能会有一种最初的兴奋,可能想让你为所有东西做鞋拔定制分配器,后来才后悔额外的负担(特别是如果,在你的兴奋中,你忘记了线程安全和对齐等问题).值得在那里轻松一点.与任何微优化一样,它通常最好是在后见之明,以及手中的分析器中进行离散应用.

  • 谢谢,这正是我要找的答案!! (2认同)

Tec*_*ton 7

内存池的基本概念是为应用程序分配大部分内存,稍后,不是使用plain new来从O/S请求内存,而是返回先前分配的内存块.

为了使这项工作,你需要自己管理内存使用,而不是依赖于操作系统; 即,您需要实现自己的版本,new并且delete仅在分配,释放或可能调整自己的内存池大小时才使用原始版本.

第一种方法是定义一个自己的类encapsules一个内存池,并提供了实现的语义自定义方法newdelete,但是从预分配的池内存取.请记住,这个池只不过是一个已经使用new并具有任意大小的内存区域.池的版本new/ deletereturn resp.指点.最简单的版本可能看起来像C代码:

void *MyPool::malloc(const size_t &size)
void MyPool::free(void *ptr)
Run Code Online (Sandbox Code Playgroud)

您可以使用模板来自动添加转换,例如

template <typename T>
T *MyClass::malloc();

template <typename T>
void MyClass::free(T *ptr);
Run Code Online (Sandbox Code Playgroud)

请注意,由于模板参数时,size_t size可以因为编译器允许您调用省略的说法sizeof(T)malloc().

返回一个简单的指针意味着您的池只能在相邻内存可用时才会增长,并且只有在未采用其"边界"的池内存时才会缩小.更具体地说,您无法重新定位池,因为这会使malloc函数返回的所有指针无效.

修复此限制的一种方法是返回指针指针,即返回T**而不是简单T*.这允许您在面向用户的部分保持不变时更改基础指针.在内部,已经为NeXT O/S做了,它被称为"句柄".要访问句柄的内容,必须打电话(*handle)->method(),或(**handle).method().最终,Maf Vosburg发明了一个伪运算符,利用运算符优先级来摆脱(*handle)->method()语法:handle[0]->method();它被称为sprong运算符.

该操作的好处是:首先,你避免了典型调用的开销newdelete,二是你的内存池保证了一个连续的内存段用于您的应用程序,即,它避免了内存碎片,从而增加CPU的缓存命中.

因此,基本上,内存池为您提供了一个加速,您可能会遇到可能更复杂的应用程序代码的缺点.但话说回来,有一些内存池的实现已被证明并且可以简单地使用,例如boost :: pool.