Published by orzz.org(). (https://orzz.org/memory-pool-basic-concepts/)
1. 内存池是什么
内存池(Memory Pool),是内存分配器(Memory Allocation)的一种表现形式。它以预存储的方式预先分配一大块内存(相对于每次请求的内存大小来说),使得绝大部分的内存请求只需要在已分配的大块内存上划分出一小块来即可。
与malloc/free相比的优点:
因为内存池绝大部分情况下是用户态、无锁、O(1)或O(logN)的时间复杂度,所以速度会比malloc/free要快很多。
尤其是在多线程环境下,系统级的malloc需要加锁,陷入内核态之后会非常的慢;而一个设计良好的多线程内存池可以让分配速度提高一个数量级。
频繁且不规律的malloc/free,尤其是大量小对象的时候,可能会导致内存释放后,空闲的内存块被已分配的内存块分割成无法合并的多块小“碎块”。此时突然申请一块大内存,虽然总的空闲内存大小是足够的,但我们却无法利用它们来完成内存分配。
正确的使用内存池,可以在一定程度上避免内存碎片的产生。
由于malloc在分配内存时,需要增加一些必要的簿记信息,也就是记录内存块信息的头部结构,因此每次内存分配都会导致一定程度的浪费。
而内存池不同,可以做到按需分配。使用得当的话内存浪费会很小。
当然了,使用内存池的缺点是会导致一定程度上的编程复杂度增加。不同方法实现的内存池有各自在使用上需要注意的地方,随随便便的用可能会引起意料之外的问题。
2. 内存池的分类
初略来说,可以分为两大类,即:定长内存池(fixed-size allocation)和变长内存池(variable-sized allocation)。
我们通常意义上所说的“内存池”,一般来说指的是定长内存池。
所谓定长内存池,即池中预存的大块内存是被划分为若干固定大小的小内存块存储的。这些小内存块一般使用链表串起来,在需要一块内存时,就从链表头部取出一块空闲内存交给外部,而释放过程则是简单的把内存挂回链表上。
因为这种内存池适用于固定大小内存块的分配和释放,因此也有人喊它“固定大小缓冲池”。
当然,最简单的定长内存池只能用来分配固定大小的内存块,不能直接被用作通用内存分配器使用。一种常规的做法是将若干个不同大小的定长内存池整合在一起,形成一个可以分配不同大小内存块的通用分配器。
这一类的内存池(或者应该说分配器)有Boost.Pool、SGI STL的allocator、Loki的SmallObjAllocator、tcmalloc、jemalloc等。
变长内存池,相对于定长来说,并不固定分配的长度,内存的分配只是在一大块空闲内存块上的指针滑动。这种做法使得它比定长内存池的分配速度更快,并且不需要多个不同的池就可以进行各种大小的内存分配,但代价是只能成批的回收内存(即多次分配一次释放),并不能像malloc/free这样成对的做分配/释放操作。使用这种分配方式的程序必须合理规划好每块内存的管理区域,因此这种内存分配方式也叫做“基于区域的内存管理”(region-based memory management)。
使用这种做法的内存池(分配器)有Obstack、Apache Portable Runtime里的apr_pool、许式伟StdExt里的AutoFreeAlloc和ScopeAlloc等。
除了上面所说的分类以外,对于通用内存分配器来说(只需要固定大小的情况定长内存池就足够了)还存在一些其他的内存池设计思路。
比如dlmalloc,以及基于dlmalloc的ptmalloc,它们采用了边界标记法(Boundary Tags),可以将多块相邻的空闲小内存(Chunks)合并成一块大内存,使得分配不同大小内存块时空间的浪费最小化。
本文只是简单介绍下相关的基本概念。后文我会慢慢展开上文所提到的各种内存池设计方案,并横向对比他们之间的优缺点。
参考文章:
Published by orzz.org(). (https://orzz.org/memory-pool-basic-concepts/)
总结得不错呦
@OWenT 多谢捧场
期待下文~0 0~原来变长内存池是这样的0 0…..