内存池场景分析
不同的场景下,内存池的设计是不一样的,以下是几个举例:
- 短链接和长连接。在kv存储中,经常以短链接为key,长链接为value。长链接的长度一般是固定的,短链接的长度一般也在某个区间内。我们在这种场景下只需要考虑固定分配的内存块。大块内存,不均匀内存等情况是不需要考虑的。
- 为每一个连接单独创建内存池。这种情况下内存池建立的时间和释放的时间和建立连接、断开连接的时点是一致的,内存块的大小根据业务场景的不同而定。
- 实现文章的存储。每篇文章的长短大小不一,需要的内存大小也不一。
我们设计内存池的不需要考虑得面面俱到,能把单一的场景解决得很好即可。比较通用且开源的内存池框架有jemalloc和tcmalloc等。
内存池的好处
- 避免了频繁的内存分配
- 避免了频繁分配内存导致的碎片化空隙。
针对固定块建立内存池
内存池包括管理用的结构体、内存页,以及内存页里被分割为多个小块。
设计内存池结构体
设计内存池结构体。内存池结构体的属性包括内存块大小、待使用的内存块的数量、下一个要使用的内存块的指针、内存页的指针。
设计内存池接口
包括初始化、销毁、分配、释放。
实现内存池的接口
内存页相关
首先,要先声明页的大小。
初始化内存池
先申请一块内存页。需要注意的是,内存页会被切割为多个内存块。每个内存块的前4个字节存放的是下一个内存块的地址,由此形成了一个单向链表便于后续管理。此处注意二级指针的用法,二级指针所指向的空间只能放置指针。
销毁内存池
直接销毁内存页即可
申请内存块
内存块是本身就存在的。申请内存块的本意是将下一个要使用的内存块的指针取出,取出完成后更新指针。
释放内存块
释放的时候,记得把被释放内存块里存储的指针指向释放前的空闲块,以便在利用该空闲块后,能接着使用本该使用的内存块。
运行结果
为每一个连接单独创建内存池
上一种内存池情况针对的情况是大小固定、释放时间不固定。这种情况是大小不固定,释放时间固定。当连接断开的时候,内存池也随之释放,所以只需要设计销毁函数不需要设计释放函数。其关键是将内存页串为链表,以及统一释放。
定义内存页节点和内存页链表管理结构体
内存页节点结构体包括:内存页剩余空间的起始地址,下一个内存页地址,刚好超出内存页末尾的地址。
内存页链表管理结构体包括:第一个内存页节点,当前使用的内存页节点,内存页的大小。
初始化内存页管理结构体
申请一个内存页,在内存页头部存放一个内存页结构体,用于形成管理对应的内存页,以及寻找下一个内存页。
同时修改内存页管理结构体。
销毁内存页节点
和正常地链表释放没有区别
申请内存使用
逻辑是如果在当前内存页可以申请到足够的内存空间直接申请。如果申请不到那么循环遍历之后的节点的空间是否足够。如果一直没有足够的空间,那么建立新的内存页节点。
运行结果