欢迎你们前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~html
本文由[amc](cloud.tencent.com/developer/u…)发表于云+社区专栏算法
在 C 语言的动态申请内存技术中,相比起 alloc
/free
系统调用,内存池(memory pool)是与如今系统中请求一大片连续的内存空间,而后在运行时根据实际须要分配出去的技术。使用内存池的优势有:apache
malloc
/free
快,由于减小了系统调用的次数,特别是频繁申请/释放内存块的状况根据分配出去的内存大小,内存池能够分为两类:性能优化
每次分配出去的内存单元(称为 unit 或者 cell)的大小为程序预先定义的值。释放内存块时,则只须要简单地挂回内存池链表中便可。又称为 “固定尺寸缓冲池”。数据结构
常规的作法是:将不一样 unit size 的内存池整合在一块儿,以知足不一样内存块大小的使用需求多线程
不分配固定长度,内存的分配只是在一大块空闲的内存上滑动。优势是分配效率很高,缺点是成批地回收内存,由于释放的内存没法直接重复利用。机器学习
使用这种须要合理规划每块内存的管理区域,因此又叫作 “基于区域的” 内存管理。使用这种作法的分配器,举例有 Apache Portable Runtime 中的 apr_pool 工具。本文不讨论这种内存池。函数
定长内存池有一些基本和必要的概念,须要定义在内存池的结构数据中。如下命名方式使用变体的匈牙利命名法,好比 nNext
,n
表示变量类型为整形。相似地,p
表示指针。工具
每次程序调用 MemPool_Alloc
获取一个内存区域后,会得到一块连续的内存区域。管理一个这样的内存区域的单元就成为内存单元 unit,有时也称做 chunk。每一个 unit 须要包含如下数据:性能
nNext
:整型数据,表示下一个可供分配的 unit 的标识号。功能请参见后问pData[]
:实际的内存区域,其大小在建立时由调用方指定一个内存块,内存块中保存着一系列的内存单元。
这个数据结构须要包含如下基本信息:
nSize
:整型数据,表示该 block 在内存中的大小nFree
:整型,表示剩下有几个 unit 未被分配nFirst
:整型,表示下一个可供分配的 unit 的标识号pNext
:指针,指向下一个 memory block一个内存池总的管理数据结构,换句话说,是一个内存池对象。
pBlock
:指针,指向第一个 memory blocknUnitSize
:整型,表示每一个 unit 的尺寸nInitSize
:整型,表示第一个 block 的 unit 个数nGrowSize
:整型,表示在第一个 block 以外再继续增长的每一个 block 的 unit 个数做为一个内存池,须要实现如下一些基本的函数接口,或者说能够是对象方法:
memPoolCreate()
建立一个 memory pool,必须的参数为 unit size,可选参数为上文 memory pool 的 nInitSize
和 nGrowSize
。
memPoolDestroy()
销毁整个 memory pool 并交还给操做系统。
memPoolAlloc()
从 memory pool 中分配一个 unit,其尺寸是预先定义的 unit size。
memPoolFree()
释放一个指定的 unit。
如今咱们用一个 unit size 为 102四、init size 为 4(每个 block 有 4 个 units)的 memory pool 为例,解释一下内存池的工做原理。下文假设整型的宽度为 4 个字节。
程序开始,调用并建立一个 memory pool。此时调用的函数为 memPoolCreate()
,程序会建立一个数据结构,相应的结构体成员及其取值以下:
当调用者第一次请求 memPoolAlloc()
时,内存池发现 block 链表为空,因而想系统申请内存,建立 memory block,并初始化以下(其中地址值为假设值):
其中 nSize = 4112 = sizeof(memPool) + nInitSize * sizeof(memUnit)
。每个 nNext
依次加一,各指代着跟着本身的下一个 unit。最后一个 unit 的 nNext
值无心义,所以不说明其取值。
而后返回须要的 unit 中的内存。返回内存的逻辑以下:
nFree
成员nFree > 0
,表示有未分配的 unit,所以继续在该 block 中查看 nFirst
成员nFirst
等于 0,表示该 block 中位置为 0 的 unit 可用。所以内存池能够将这个 unit 中的 pData
地址返回给调用方。 pData
的地址值计算方式为:pBlock + sizeof(memBlock) + nFirst * (sizeof(memUnit)) + sizeof(nNext) = 0x10010
nFree
减一nFirst
的值,标记下一个可用的 unit。注意这里的 nFirst
切切不能简单地加一,而是取返回给调用方的 unit 所对应的 nNext
的值,也就是下图(2)
处原来的值 1
pData
的地址值返回。为便于说明,这块区域咱们标记为 CA操做后各数据结构的状态以下:
第二次调用 alloc
的状况相似。调用后各数据结构的状态以下:
咱们先看看结果:
0x10000 <= 0x10010 <= (0x10000 + 4112)
)。再计算偏移值能够很快得出其对应的 nNext
标号,也就是上图中的(2)
位置。nFirst
的值,参见上前幅图,nFirst
的值为 3,表示位置(3)
处的 unit 是可用的。所以咱们首先把 (2)
处的 nNext
值设置为 3,将其加回到可用 unit 的链表中nFirst
的值修改成 0
,也就是表明刚刚回收回来的 unit 的标号,而(2)
处的值赋值为 2,表示b(3)
的 unit其实能够看到,上面就是一个简单的链表操做。根据上面的过程,若是 CB 也释放了的话,那么 memory pool 的状态则会变成这样:
到这个时候,因为整个 block 已经彻底回收了(nFree == nInitSize
),那么根据不一样的策略,能够考虑将整个 block 从内存中释放掉。
咱们回到 alloc
的逻辑中,能够看到内存池最开始会检查 block 的 nFree
成员。若是 nFree == 0
的时候,那么就会在该 block 的 pNext
中去找到下一个 block,再去检查 nFree
。若是发现 block 链表已经结束了,那就意味着当前全部的 block 已满,必须建立新的 block。
在实际设计中,咱们须要考虑选取合适的 init size 和 grow size 值。从上面的算法中能够看到,若是 alloc
/free
调用很是频繁时,第一个 block 的使用效率是很是高的。
pNext
来维护链表,也就是只有一个 block,而且内存的使用有一个明确且受控的上限值。这常常用在没有 malloc
系统调用的 RTOS 或者是一些对内存很是敏感的嵌入式系统中。此文已由做者受权腾讯云+社区发布,更多原文请点击
搜索关注公众号「云加社区」,第一时间获取技术干货,关注后回复1024 送你一份技术课程大礼包!
海量技术实践经验,尽在云加社区!