内存池 (1)
1.1 为什么要使用内存池
在编程实践中,不可避免地要大量用到堆上的内存。例如在程序中维护一个链表的数据结构时,每次新增或者删除一个链表的节点,都需要从内存堆上分配或者释放一定的内存;在维护一个动态数组时,如果动态数组的大小不能满足程序需要时,也要在内存堆上分配新的内存空间。
利用默认的内存管理函数new/delete或malloc/free在堆上分配和释放内存会有一些额外的开销。
系统在接收到分配一定大小内存的请求时,首先查找内部维护的内存空闲块表,并且需要根据一定的算法(例如分配最先找到的不小于申请大小的内存块给请求者,或者分配最适于申请大小的内存块,或者分配最大空闲的内存块等)找到合适大小的空闲内存块。如果该空闲内存块过大,还需要切割成已分配的部分和较小的空闲块。然后系统更新内存空闲块表,完成一次内存分配。类似地,在释放内存时,系统把释放的内存块重新加入到空闲内存块表中。如果有可能的话,可以把相邻的空闲块合并成较大的空闲块。
默认的内存管理函数还考虑到多线程的应用,需要在每次分配和释放内存时加锁,同样增加了开销。可见,如果应用程序频繁地在堆上分配和释放内存,则会导致性能的损失。并且会使系统中出现大量的内存碎片,降低内存的利用率。
默认的分配和释放内存算法自然也考虑了性能,然而这些内存管理算法的通用版本为了应付更复杂、更广泛的情况,需要做更多的额外工作。而对于某一个具体的应用程序来说,适合自身特定的内存分配释放模式的自定义内存池则可以获得更好的性能。
1.2 内存池的分类
应用程序自定义的内存池根据不同的适用场景又有不同的类型。
从线程安全的角度来分,内存池可以分为单线程内存池和多线程内存池。单线程内存池整个生命周期只被一个线程使用,因而不需要考虑互斥访问的问题;多线程内存池有可能被多个线程共享,因此则需要在每次分配和释放内存时加锁。相对而言,单线程内存池性能更高,而多线程内存池适用范围更广。
从内存池可分配内存单元大小来分,可以分为固定内存池和可变内存池。所谓固定内存池是指应用程序每次从内存池中分配出来的内存单元大小事先已经确定,是固定不变的;而可变内存池则每次分配的内存单元大小可以按需变化,应用范围更广,而性能比固定内存池要低。
从我们程序要求来看,需要做的是一个多线程固定内存池。
1.3 内存池的优点
内存池的操作非常迅速,它在性能优化方面的优点主要如下。
(1) 针对特殊情况,例如需要频繁分配释放固定大小的内存对象时,不需要复杂的分配算法。也不需要维护内存空闲表的额外开销,从而获得较高的性能。
(2) 由于开辟一定数量的连续内存空间作为内存池块,因而一定程度上提高了程序局部性,提升了程序性能。
(3) 比较容易控制页边界对齐和内存字节对齐,没有内存碎片的问题。
2.1 一个内存池的实现实例
这是一个应用于多线程环境且分配单元大小固定的内存池,用来为执行时会动态频繁地创建且可能会被多次创建的类对象或者结构体分配内存。首先讲解该内存池的数据结构声明及图示,接着描述其原理及行为特征,然后逐一讲解实现细节。
2.2 一个内存池的实现实例
内存池类MemoryPool的声明如下:
![](https://i-blog.csdnimg.cn/blog_migrate/fe0ade8ea2f9e21c0972c5e703d05be6.jpeg)
伪码 (1)
MemoryPool只提供两个对外接口,分配内存函数Alloc() ,和释放内存函数Free()。另外两个函数是用来调试用的,WriteMemToFile()可以将内存池里的内存数据保存到硬盘里,IsValidPointer()用来检查所给的指针是否在内存池的内存里。
MemoryPool的内存是由许多大小固定的内存块组成的,并由下面的结构来标记每小块内存。
![](https://i-blog.csdnimg.cn/blog_migrate/cc9a96721680283e9d600e3e6e6e8866.jpeg)
伪码 (2)
2.3 MemoryPool的数据结构
MemoryPool由两部分构成,一是向操作系统申请的内存空间,二是用来管理这些内存的结构链表。下面说明MemoryPool的结构链表是如何地进行工作。
固定内存池由一系列固定大小的内存块组成,每一个内存块又包含了固定数量和大小的内存单元。
![](http://hiphotos.baidu.com/huzhihao/pic/item/54dcb719ad31e95842a9adf8.jpg)
图 (1)
如图(1)所示,蓝色表示内存池向系统申请的内存块,橙色表示内存池分配给应用的内存空间,紫色为系统的其他内存空间。该内存池一共包含3个内存块(Block),在内存池初次生成时,只向系统申请了一个内存块,返回的指针作为整个内存池的头指针。之后随着应用程序对内存的不断需求,内存池判断需要动态扩大时,才再次向系统申请新的内存块,并把所有这些内存块通过指针链接起来。对于操作系统来说,它已经为该应用程序分配了3个不等大小的内存块(由应用控制)。由于是大小固定的,所以分配的速度比较快;而对于应用程序来说,其内存池开辟了一定大小,内存池内部却还有剩余的空间。
下面我们来看内存池是如何工作的:
2.3.1 初始化
(1) 首先,在MemoryPool构造时,开始第一次创建内存池,如图(2)所示,它创建一个500字节的内存块(Block),而这个内存块由5个连续的小块(Chunk)内存构成。由于它还没有被分配(Allocated),所以它还是空闲的(Free)。
![](http://hiphotos.baidu.com/huzhihao/pic/item/1d9f6922ccbc7eb94723e8f9.jpg)
图 (2)
(2) 在创建了内存之后紧接着是创建管理它的结构链表,结构体是上面讲到的struck MemoryChunk,每一小块内存(Chunk)和一个MemoryChunk一一对应。
![](https://i-blog.csdnimg.cn/blog_migrate/91ded9b97171b61f98c759bc168ed2c1.jpeg)
图 (3)
再来看MemoryChunk的每个成员作用,其中,Data是指向每小块内存(Chunk)的首地址,将Data返回给应用,应用就可以读写这块内存了。DataSize表示从当前块开始向后连续空闲的内存大小,如图 (2)。IsAlloc表示此小块内存(Chunk)是否已经被分配给应用。Next和Last分别指向下一结构和上一结构,因此这是一个双向链表。这样内存池和链表都建立好了。
2.3.2 分配内存(Alloc)
假设应用需要200字节,就调用函数Alloc(200) ,MemoryPool开始从链表头(m_pFristChunk)进行搜索,如果DataSize大于200,并且IsAlloc为false ,则返回Data指针。具体流程如下:
![](https://i-blog.csdnimg.cn/blog_migrate/7eefecd4e1ca2c676657ad107c8ed75f.jpeg)
图 (4)
![](https://i-blog.csdnimg.cn/blog_migrate/b0b62eab3a5563b484aad27d1c8e5e21.jpeg)
图 (5)
如图(5)所示,分配完的结果,在返回Data指针前,需要再遍历一次链表,改写DataSize的大小和IsAlloc的状态。假设这时候需要再分配200字节,根据图(4)的流程图我们可以很快地知道结果:
![](https://i-blog.csdnimg.cn/blog_migrate/631d173da0c0336ddb062051e17293df.jpeg)
图 (6)
如果这时候需要再分配300字节,显然剩余的空间不够我们分配了,这时候就需要向操作系统申请新的空间。还是可以根据图(4)的流程图得到结果:
![](https://i-blog.csdnimg.cn/blog_migrate/eadf7428e93aab673bb2379cd9977f99.jpeg)
图 (7)
2.3.3 释放内存(Free)
释放内存就比较简单些,只需在链表上标记一下IsAlloc就可以了,我们来看流程图:
![](https://i-blog.csdnimg.cn/blog_migrate/4c5e9d193fe3f98564a46fb8175b2984.jpeg)
图 (8)
假设要释放刚才分配的2个200字节的空间,下面给出内存分配示意图:
![](https://i-blog.csdnimg.cn/blog_migrate/c519c8846d01481beb0aa0088af0c251.jpeg)
图 (9)
图(9) ,释放最前面200字节,改IsAlloc为false。
![](https://i-blog.csdnimg.cn/blog_migrate/e08d7f51fb8dace439c8a835e612494f.jpeg)
图 (10)
图(10) ,释放第3、4小块内存,除了改IsAlloc为false,还对链表进行了反向遍历,重置DataSize。
2.4 使用方法
从上面的分析可以看到,该内存池主要有两个对外接口函数,即Alloc和Free。Alloc返回所申请的分配单元(固定大小内存),Free则回收传入的指针代表的分配单元的内存给内存池。分配的信息则通过MemoryPool的构造函数指定,包括分配单元大小、内存池中所含分配单元的个数。
综上所述,当需要提高某些关键类对象的申请/回收效率时,可以考虑将该类所有生成对象所需的空间都从某个这样的内存池中开辟。在销毁对象时,只需要返回给该内存池。"一个类的所有对象都分配在同一个内存池对象中"这一需求很自然的设计方法就是为这样的类声明一个静态内存池对象,同时为了让其所有对象都从这个内存池中开辟内存,而不是缺省的从进程堆中获得,需要为该类重载一个new运算符。因为相应地,回收也是面向内存池,而不是进程的缺省堆,还需要重载一个delete运算符。在new运算符中用内存池的Alloc函数满足所有该类对象的内存请求,而销毁某对象则可以通过在delete运算符中调用内存池的Free完成。
3 内存池性能
怎样才能让内存池高性能的运作?内存池主要耗时是在链表遍历上,Chunk的大小越小空间利用率就越高,但是时间利用率就越低(链表越长),这需要找到两者的平衡。要针对虚拟机内存使用情况进行分析,用统计学处理可以得到较好的效果。
1.1 为什么要使用内存池
在编程实践中,不可避免地要大量用到堆上的内存。例如在程序中维护一个链表的数据结构时,每次新增或者删除一个链表的节点,都需要从内存堆上分配或者释放一定的内存;在维护一个动态数组时,如果动态数组的大小不能满足程序需要时,也要在内存堆上分配新的内存空间。 利用默认的内存管理函数new/delete或malloc/free在堆上分配和释放内存会有一些额外的开销。 系统在接收到分配一定大小内存的请求时,首先查找内部维护的内存空闲块表,并且需要根据一定的算法(例如分配最先找到的不小于申请大小的内存块给请求者,或者分配最适于申请大小的内存块,或者分配最大空闲的内存块等)找到合适大小的空闲内存块。如果该空闲内存块过大,还需要切割成已分配的部分和较小的空闲块。然后系统更新内存空闲块表,完成一次内存分配。类似地,在释放内存时,系统把释放的内存块重新加入到空闲内存块表中。如果有可能的话,可以把相邻的空闲块合并成较大的空闲块。 默认的内存管理函数还考虑到多线程的应用,需要在每次分配和释放内存时加锁,同样增加了开销。可见,如果应用程序频繁地在堆上分配和释放内存,则会导致性能的损失。并且会使系统中出现大量的内存碎片,降低内存的利用率。 默认的分配和释放内存算法自然也考虑了性能,然而这些内存管理算法的通用版本为了应付更复杂、更广泛的情况,需要做更多的额外工作。而对于某一个具体的应用程序来说,适合自身特定的内存分配释放模式的自定义内存池则可以获得更好的性能。 1.2 内存池的分类 应用程序自定义的内存池根据不同的适用场景又有不同的类型。 从线程安全的角度来分,内存池可以分为单线程内存池和多线程内存池。单线程内存池整个生命周期只被一个线程使用,因而不需要考虑互斥访问的问题;多线程内存池有可能被多个线程共享,因此则需要在每次分配和释放内存时加锁。相对而言,单线程内存池性能更高,而多线程内存池适用范围更广。 从内存池可分配内存单元大小来分,可以分为固定内存池和可变内存池。所谓固定内存池是指应用程序每次从内存池中分配出来的内存单元大小事先已经确定,是固定不变的;而可变内存池则每次分配的内存单元大小可以按需变化,应用范围更广,而性能比固定内存池要低。 从我们程序要求来看,需要做的是一个多线程固定内存池。 1.3 内存池的优点 内存池的操作非常迅速,它在性能优化方面的优点主要如下。 (1) 针对特殊情况,例如需要频繁分配释放固定大小的内存对象时,不需要复杂的分配算法。也不需要维护内存空闲表的额外开销,从而获得较高的性能。 (2) 由于开辟一定数量的连续内存空间作为内存池块,因而一定程度上提高了程序局部性,提升了程序性能。 (3) 比较容易控制页边界对齐和内存字节对齐,没有内存碎片的问题。 |
这是一个应用于多线程环境且分配单元大小固定的内存池,用来为执行时会动态频繁地创建且可能会被多次创建的类对象或者结构体分配内存。首先讲解该内存池的数据结构声明及图示,接着描述其原理及行为特征,然后逐一讲解实现细节。
2.2 一个内存池的实现实例
内存池类MemoryPool的声明如下:
![](https://i-blog.csdnimg.cn/blog_migrate/fe0ade8ea2f9e21c0972c5e703d05be6.jpeg)
伪码 (1)
MemoryPool只提供两个对外接口,分配内存函数Alloc() ,和释放内存函数Free()。另外两个函数是用来调试用的,WriteMemToFile()可以将内存池里的内存数据保存到硬盘里,IsValidPointer()用来检查所给的指针是否在内存池的内存里。
MemoryPool的内存是由许多大小固定的内存块组成的,并由下面的结构来标记每小块内存。
![](https://i-blog.csdnimg.cn/blog_migrate/cc9a96721680283e9d600e3e6e6e8866.jpeg)
伪码 (2)
MemoryPool由两部分构成,一是向操作系统申请的内存空间,二是用来管理这些内存的结构链表。下面说明MemoryPool的结构链表是如何地进行工作。
固定内存池由一系列固定大小的内存块组成,每一个内存块又包含了固定数量和大小的内存单元。
![](http://hiphotos.baidu.com/huzhihao/pic/item/54dcb719ad31e95842a9adf8.jpg)
图 (1)
下面我们来看内存池是如何工作的:
2.3.1 初始化
(1) 首先,在MemoryPool构造时,开始第一次创建内存池,如图(2)所示,它创建一个500字节的内存块(Block),而这个内存块由5个连续的小块(Chunk)内存构成。由于它还没有被分配(Allocated),所以它还是空闲的(Free)。
![](http://hiphotos.baidu.com/huzhihao/pic/item/1d9f6922ccbc7eb94723e8f9.jpg)
图 (2)
(2) 在创建了内存之后紧接着是创建管理它的结构链表,结构体是上面讲到的struck MemoryChunk,每一小块内存(Chunk)和一个MemoryChunk一一对应。
![](https://i-blog.csdnimg.cn/blog_migrate/91ded9b97171b61f98c759bc168ed2c1.jpeg)
图 (3)
再来看MemoryChunk的每个成员作用,其中,Data是指向每小块内存(Chunk)的首地址,将Data返回给应用,应用就可以读写这块内存了。DataSize表示从当前块开始向后连续空闲的内存大小,如图 (2)。IsAlloc表示此小块内存(Chunk)是否已经被分配给应用。Next和Last分别指向下一结构和上一结构,因此这是一个双向链表。这样内存池和链表都建立好了。
2.3.2 分配内存(Alloc)
假设应用需要200字节,就调用函数Alloc(200) ,MemoryPool开始从链表头(m_pFristChunk)进行搜索,如果DataSize大于200,并且IsAlloc为false ,则返回Data指针。具体流程如下:
![](https://i-blog.csdnimg.cn/blog_migrate/7eefecd4e1ca2c676657ad107c8ed75f.jpeg)
图 (4)
![](https://i-blog.csdnimg.cn/blog_migrate/b0b62eab3a5563b484aad27d1c8e5e21.jpeg)
图 (5)
如图(5)所示,分配完的结果,在返回Data指针前,需要再遍历一次链表,改写DataSize的大小和IsAlloc的状态。假设这时候需要再分配200字节,根据图(4)的流程图我们可以很快地知道结果:
![](https://i-blog.csdnimg.cn/blog_migrate/631d173da0c0336ddb062051e17293df.jpeg)
图 (6)
如果这时候需要再分配300字节,显然剩余的空间不够我们分配了,这时候就需要向操作系统申请新的空间。还是可以根据图(4)的流程图得到结果:
![](https://i-blog.csdnimg.cn/blog_migrate/eadf7428e93aab673bb2379cd9977f99.jpeg)
图 (7)
2.3.3 释放内存(Free)
释放内存就比较简单些,只需在链表上标记一下IsAlloc就可以了,我们来看流程图:
![](https://i-blog.csdnimg.cn/blog_migrate/4c5e9d193fe3f98564a46fb8175b2984.jpeg)
图 (8)
假设要释放刚才分配的2个200字节的空间,下面给出内存分配示意图:
![](https://i-blog.csdnimg.cn/blog_migrate/c519c8846d01481beb0aa0088af0c251.jpeg)
图 (9)
图(9) ,释放最前面200字节,改IsAlloc为false。
![](https://i-blog.csdnimg.cn/blog_migrate/e08d7f51fb8dace439c8a835e612494f.jpeg)
图 (10)
图(10) ,释放第3、4小块内存,除了改IsAlloc为false,还对链表进行了反向遍历,重置DataSize。
2.4 使用方法
从上面的分析可以看到,该内存池主要有两个对外接口函数,即Alloc和Free。Alloc返回所申请的分配单元(固定大小内存),Free则回收传入的指针代表的分配单元的内存给内存池。分配的信息则通过MemoryPool的构造函数指定,包括分配单元大小、内存池中所含分配单元的个数。
综上所述,当需要提高某些关键类对象的申请/回收效率时,可以考虑将该类所有生成对象所需的空间都从某个这样的内存池中开辟。在销毁对象时,只需要返回给该内存池。"一个类的所有对象都分配在同一个内存池对象中"这一需求很自然的设计方法就是为这样的类声明一个静态内存池对象,同时为了让其所有对象都从这个内存池中开辟内存,而不是缺省的从进程堆中获得,需要为该类重载一个new运算符。因为相应地,回收也是面向内存池,而不是进程的缺省堆,还需要重载一个delete运算符。在new运算符中用内存池的Alloc函数满足所有该类对象的内存请求,而销毁某对象则可以通过在delete运算符中调用内存池的Free完成。
3 内存池性能
怎样才能让内存池高性能的运作?内存池主要耗时是在链表遍历上,Chunk的大小越小空间利用率就越高,但是时间利用率就越低(链表越长),这需要找到两者的平衡。要针对虚拟机内存使用情况进行分析,用统计学处理可以得到较好的效果。
内存池应用实例
用C语言写了一个内存池
头文件:
mempool.h
#ifndef _MEM_POOL_H
#define _MEM_POOL_H
//创建内存池,mem_size内存池的大小
//内存池的实际大小是mem_size向上取整,2的幂次
//调用mem_create函数,不会分配空间,在第一次调用mem_alloc_ex时分配
//最小是1M,小于1M的,自动变成1M
void* mpool_create(size_t mem_size);
//删除内存池
void mpool_destroy(void *h);
//设置默认内存池的大小
void mpool_default_size(size_t size);
//设置或得到默认的内存池(如果pool为空,取得当前的内存池,如果不为空,设置默认内存池)
void* mpool_default_pool(void *pool);
//分配内存
//allocsize为实际可用的内存大小,一般分配的内存是>=size的
//每次分配内存都是按2的幂次来分配的,由于还有头结构以及其它信息,实际可用大小并不是2的幂次
//如果h传入的是NULL,程序会使用默认的内存池,静态变量保存(如果是在so中,需要保证malloc和free次数一致,不允许so内malloc,外部free)
void* mpool_alloc_ex(void *h, size_t size, char *filename, int lineno, size_t *allocsize);
//释放内存
void mpool_free_ex(void *p);
//重新分配内存
//data==NULL 分配内存
//size==0 释放内存
//size>原始大小检查后面是否有空余内存,有则直接返回,没有先malloc,再memmove,然后free
//size<原始大小 将内存分裂,后面的变为可用内存,供以后内存分配
//allocsize 为实际可用的内存大小,>=size
void* mpool_realloc_ex(void *h, void *data, size_t size, char *filename, int lineno, size_t *allocsize);
//dump内存池中所有已分配的内存,输出到out中,最大长度maxlen
//如果out为NULL,输出到stderr
//如果有文件名,输出文件名和行号
//如果h为NULL,会dump默认的内存池
size_t mpool_dump(void *h, char *out, size_t maxlen);
//检查内存池,是否有越界产生
//如果出错,程序直接退出 assert操作
//如果h为NULL,检查默认的内存池
void mpool_check(void *h);
//向上取整,返回的是位数,需要1<<得到真实的数字
char up_power2(size_t m);
//使用时为了方便,可以定义如下的宏
//整个程序的运行,只需要一个内存池就可以
#define malloc(size)
mpool_alloc_ex(NULL, size, __FILE__, __LINE__, NULL)
#define realloc(p,size)
mpool_realloc_ex(NULL, p, size, __FILE__, __LINE__, NULL)
#define free(p)
mpool_free_ex(p)
#endif
源代码文件:mempool.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
#include <assert.h>
#include <pthread.h>
#ifdef WIN32
#define snprintf _snprintf
#endif
#define IS_64BIT
(sizeof(long)==8)
//是否是64位机器
#define MEM_THREAD
//支持多线程
//内存魔数
#define MEM_MAGIC
0xfdfdfdfd
#define MEM_LARGE_SIZE
100 * 1024 * 1024
//超过100M的认为是大内存
#define MEM_DEFAULT_POOL_SIZE
32 * 1024 * 1024
//默认内存池的大小
#define MEM_MERGE_PER_FREE
100
//合并点,free多少次之后才进行合并
typedef struct MemPoolHead MemPoolHead;
typedef struct MemPool MemPool;
typedef struct MemNode MemNode;
//双链表 头结点和尾节点
typedef struct{
MemNode
*head;
//头结点
MemNode *tail;
//尾结点
int
num;
//结点个数
}DLink;
//内存节点
头 + 内容 + 文件名
struct MemNode{
MemPool
*pool;
//所在的内存池
MemNode
*next;
//下一个节点
MemNode
*pre;
//上一个节点
MemNode
*near_next;
//相邻的下一个节点
uint8_t
sbit:6;
//该块内存的大小,实际大小(1<<sbit)
uint8_t
is_used:1;
//是否已用
uint8_t
is_large:1;//是否是大内存
uint8_t
filename_len;
//文件名长度
uint16_t
lineno;
//行号
uint32_t
magic;
//魔数
size_t
data_len;
//可使用的内存大小(空闲内存是整个内存块的大小)
};
//内存池的头结点
struct MemPoolHead{
int pool_num;
//内存池的个数
size_t pool_size;
//每个内存池的大小
uint8_t pool_link_num;
//每个内存池slot的个数
MemPool
*head;
//内存池头指针
MemPool
*tail;
//内存池尾指针
DLink
large_link;
//大内存的双链表
DLink
used_link;
//已分配内存的双链表
uint32_t malloc_count;
//malloc的次数
uint32_t free_count;
//free的次数
MemNode
**merge_nodes;
//用于内存块合并的内存空间
int
max_node_num;
//merge_nodes的个数
int
magic;
//魔数
#ifdef MEM_THREAD
pthread_mutex_t lock;
//互斥锁
#endif
};
//内存池
struct MemPool{
MemPoolHead
*head;
//内存池头
MemPool
*next;
//下一个内存池
void
*start;
//内存池的起始位置
void
*end;
//内存池的结束位置
DLink
links[64];
//指向每个内存块的双链表
int
used_num;
//当前已分配的内存块数量
uint32_t
malloc_count;
//malloc次数
uint32_t
free_count;
//free次数
uint32_t
merge_point;
//合并点
int
magic;
//魔数
};
static void *s_mempool;
//向上取整,得到最小的2的幂次
char up_power2(size_t m) {
static char s_table[65536];
if(s_table[1] == 0){
//初始化查找表
char i, *p = s_table + 1;
for(i = 0; i < 16; i ++){
memset(p, i + 1, 1 << i);
p += 1 << i;
}
}
m --;
return m > 0xffffffffffffULL ? (s_table[m >> 48] + 48) : (m > 0xffffffffUL ? (s_table[m >> 32] + 32) : (m > 0xffff ? s_table[m >> 16] + 16 : s_table[m]));
}
//添加一个节点 0-释放内存,1-分配内存 2-大内存
static void _mem_add_node(MemPool *pool, DLink *link, MemNode *node, int sizebit, size_t realsize, int is_used, int is_large, char *filename, int lineno){
size_t blocksize;
//内存块的总大小
node->magic = MEM_MAGIC;
node->sbit = sizebit;
node->pool = pool;
node->pre = NULL;
node->lineno = lineno;
node->filename_len = filename ? strlen(filename) + 1 : 0;
node->is_used = is_used;
node->is_large = is_large;
blocksize = is_large ? sizeof(MemNode) + node->filename_len + realsize : ((size_t)1 << sizebit);
node->near_next = (MemNode*)((char*)node + blocksize);
node->next = NULL;
node->pre = link->tail;
if(link->head){
link->tail->next = node;
link->tail = node;
}else{
link->head = link->tail = node;
}
link->num ++;
if(is_used){
node->data_len = blocksize - sizeof(MemNode) - node->filename_len;
}else node->data_len = blocksize;
//结尾添加信息(文件名)
if(filename){
memcpy((char*)node->near_next - node->filename_len, filename, node->filename_len);
}
}
//从双链表中删除一个节点
static void _mem_del_node(DLink *link, MemNode *node){
node->pre ? (node->pre->next = node->next) : (link->head = node->next);
node->next ? (node->next->pre = node->pre) : (link->tail = node->pre);
link->num --;
}
//删除内存池(释放所有内存)
void mem_destroy(void *h){
MemPoolHead *pool_head;
MemPool *pool;
MemPool *next_pool;
if(h == NULL){
return;
}
pool_head = (MemPoolHead*)h;
assert(pool_head->magic == MEM_MAGIC);
pool = pool_head->head;
while(pool){
next_pool = pool->next;
free(pool);
pool = next_pool;
}
free(h);
}
//删除一个pool
static void _mem_del(MemPoolHead *pool_head, MemPool *pool){
MemPool *last_pool = pool_head->head;
while(last_pool->next != pool){
//找到上一个pool
last_pool = last_pool->next;
}
last_pool->next = pool->next;
if(pool_head->tail == pool){
//修正尾指针
pool_head->tail = last_pool;
}
pool_head->pool_num --;
free(pool);
}
//新建一个pool
MemPool* _mem_create(MemPoolHead *pool_head){
MemPool *pool;
//分配内存
pool = malloc(sizeof(MemPool) + pool_head->pool_size);
if(pool == NULL){
return NULL;
}
memset(pool, 0, sizeof(MemPool));
pool->head = pool_head;
pool_head->pool_num ++;
if(pool_head->head == NULL){
pool_head->head = pool_head->tail = pool;
}else{
pool_head->tail->next = pool;
pool_head->tail = pool;
}
pool->next = NULL;
pool->start = pool + 1;
pool->end = (char*)pool->start + pool_head->pool_size;
pool->magic = MEM_MAGIC;
return pool;
}
//初始化一个内存池
void* mpool_create(size_t pool_size){
MemPoolHead *pool_head;
int bit;
//分配内存
pool_head = malloc(sizeof(MemPoolHead));
if(pool_head == NULL){
return NULL;
}
memset(pool_head, 0, sizeof(MemPoolHead));
if(pool_size < 1024 * 1024){
pool_size = 1024 * 1024;
}
bit = up_power2(pool_size);
pool_size = (size_t)1 << bit;
pool_head->pool_size = pool_size;
pool_head->pool_link_num = bit + 1;
pool_head->magic = MEM_MAGIC;
#ifdef MEM_THREAD
pthread_mutex_init(&pool_head->lock, NULL);
#endif
return pool_head;
}
//将一块内存分裂出一块来分配空间
static void _mem_split(MemPool *pool, MemNode *node, int totalbit, int sbit, char *filename, int lineno){
_mem_add_node(pool, &pool->head->used_link, node, sbit, 0, 1, 0, filename, lineno);
while(sbit < totalbit){
node = node->near_next;
_mem_add_node(pool, pool->links + sbit, node, sbit, 0, 0, 0, NULL, 0);
sbit ++;
}
}
//分配大块内存
static MemNode* _mem_large(MemPoolHead *pool_head, size_t realsize, char *filename, int lineno){
MemNode *node;
size_t size = sizeof(MemNode) + realsize + (filename ? strlen(filename) + 1 : 0);
node = (MemNode*)malloc(size);
if(node == NULL){
return NULL;
}
_mem_add_node((MemPool*)pool_head, &pool_head->large_link, node, 0, realsize, 1, 1, filename, lineno);
return node;
}
//按照大小排序
static int _compare_point(const void *a, const void *b){
MemNode *node1 = *(MemNode**)a;
MemNode *node2 = *(MemNode**)b;
return node1 - node2;
}
//合并内存碎片
static int _mem_merge(MemPool *pool, int sizebit){
MemPoolHead *pool_head = pool->head;
MemNode *node, **merge_nodes;
int num, i, fitbit;
DLink *links = pool->links;
num = 0;
for(i = 0; i < pool_head->pool_link_num; i ++){
num += links[i].num;
}
if(num == 0){
//没有空闲内存块
return 0;
}
if(num > pool_head->max_node_num){
//申请合并需要的临时内存
pool_head->merge_nodes = realloc(pool_head->merge_nodes, sizeof(MemNode*) * num);
assert(pool_head->merge_nodes != NULL);
pool_head->max_node_num = num;
}
//初始化待合并的所有空闲内存块
merge_nodes = pool_head->merge_nodes;
num = 0;
for(i = 0; i < pool_head->pool_link_num; i ++){
node = links[i].head;
while(node){
merge_nodes[num++] = node;
node = node->next;
}
}
//按大小排序所有空闲内存块
qsort(merge_nodes, num, sizeof(MemNode*), _compare_point);
fitbit = 0;
for(i = 0; i < num - 1; i ++){
int j, k, sbit;
size_t total, size;
if(merge_nodes[i]->near_next != merge_nodes[i + 1]){
//检查内存块是否相邻
continue;
}
//得到最后一个相邻的结点
for(j = i + 1; j < num - 1; j ++){
if(merge_nodes[j]->near_next != merge_nodes[j + 1]){
break;
}
}
//检查是否需要进行合并
for(k = i; k < j; k ++){
if(merge_nodes[k]->sbit >= merge_nodes[k + 1]->sbit){
break;
}
}
if(k == j){
//已经有序,不需要合并
i = j;
continue;
}
//待合并的内存块从i到j,包括j
total = 0;
for(k = i; k < j + 1; k ++){
node = merge_nodes[k];
total += node->data_len;
_mem_del_node(links + node->sbit, node);
}
//开始合并
node = merge_nodes[i];
while(total){
sbit = up_power2(size = total & (total ^ (total - 1)));
//使用位运算做拆分,完美的一句话
_mem_add_node(pool, links + sbit, node, sbit, 0, 0, 0, NULL, 0);
total -= size;
node = node->near_next;
if(sbit >= sizebit){
if(fitbit == 0){
fitbit = sbit;
}else if(sbit < fitbit){
fitbit = sbit;
}
}
}
i = j;
}
return fitbit;
}
//分配内存
static MemNode* _mem_alloc(MemPoolHead *pool_head, size_t size, char *filename, int lineno){
MemPool *pool;
MemNode *node;
size_t
realsize;
//加上内存头之后的大小
int filename_len, sizebit, newbit;
//内存组成:头+data+filename
filename_len = filename ? strlen(filename) + 1 : 0;
realsize = sizeof(MemNode) + size + filename_len;
sizebit = up_power2(realsize);
//待分配的内存超过了可分配的最大内存
if(sizebit >= pool_head->pool_link_num || size > MEM_LARGE_SIZE){
return _mem_large(pool_head, size, filename, lineno);
}
//遍历内存池,找到最合适的内存块
pool = pool_head->head;
while(pool){
DLink *links;
if(pool->used_num == 0){
pool->malloc_count ++;
pool->used_num ++;
_mem_split(pool, pool->start, pool_head->pool_link_num - 1, sizebit, filename, lineno);
return pool->start;
}
links = pool->links;
newbit = sizebit;
while(newbit < pool_head->pool_link_num){
if(links[newbit].head){
//找到合适的可用内存块
pool->malloc_count ++;
pool->used_num ++;
node = links[newbit].head;
_mem_del_node(links + newbit, node);
_mem_split(pool, node, newbit, sizebit, filename, lineno);
return node;
}
newbit ++;
}
if(pool->next == NULL){
//检查是否有内存池可以合并,如果合并后可以分配,则分配
pool = pool_head->head;
while(pool){
if(pool->free_count - pool->merge_point > MEM_MERGE_PER_FREE){
newbit = _mem_merge(pool, sizebit);
pool->merge_point = pool->free_count;
if(newbit){
//合并后,可以分配内存了
pool->malloc_count ++;
pool->used_num ++;
node = links[newbit].head;
_mem_del_node(links + newbit, node);
_mem_split(pool, node, newbit, sizebit, filename, lineno);
return node;
}
}
pool = pool->next;
}
break;
}else{
pool = pool->next;
}
}
//没有可用的内存块,新建一个内存池
pool = _mem_create(pool_head);
if(pool){
pool->malloc_count ++;
pool->used_num ++;
_mem_split(pool, pool->start, pool_head->pool_link_num - 1, sizebit, filename, lineno);
return pool->start;
}
return NULL;
}
//释放一个结点
static void _mem_free(MemPoolHead *pool_head, MemNode *node){
//检查是否为大内存
if(node->is_large == 1){
_mem_del_node(&pool_head->large_link, node);
free(node);
}else{
MemPool *pool = node->pool;
_mem_del_node(&pool_head->used_link, node);
pool->used_num --;
pool->free_count ++;
if(pool->used_num == 0){
//所有内存全部释放了
if(pool_head->head != pool){
//不是第一个,释放内存池
_mem_del(pool_head, pool);
}else{
//将内存块初始化为一个整体
memset(pool->links, 0, sizeof(DLink) * pool_head->pool_link_num);
pool->merge_point = pool->free_count;
}
}else{
_mem_add_node(pool, pool->links + node->sbit, node, node->sbit, 0, 0, 0, NULL, 0);
}
}
}
//重新分配内存
static MemNode* _mem_realloc(MemPoolHead *pool_head, MemNode *node, size_t newsize, char *filename, int lineno){
MemPool *pool = node->pool;
MemNode *used_node = node;
int filename_len, nbit;
size_t
realsize, size, totalsize;
realsize = used_node->data_len;
//大内存节点,直接执行realloc
if(node->is_large){
MemNode tmp_node;
//先保存结点信息,防止realloc删除之后,指针信息丢失
tmp_node.pre = node->pre;
tmp_node.next = node->next;
size = sizeof(MemNode) + newsize + (filename ? strlen(filename) + 1 : 0);
//执行realloc
used_node = realloc(node, size);
if(used_node){
_mem_del_node(&pool_head->large_link, &tmp_node);
_mem_add_node((MemPool*)pool_head, &pool_head->large_link, used_node, 0, newsize, 1, 1, filename, lineno);
return used_node;
}else{
return NULL;
}
}
filename_len = filename ? strlen(filename) + 1 : 0;
nbit = up_power2(newsize + sizeof(MemNode) + filename_len);
if(nbit < node->sbit){
//realloc变小
//分裂内存块
_mem_del_node(&pool_head->used_link, node);
_mem_split(pool, node, node->sbit, nbit, filename, lineno);
return node;
}else if(nbit == node->sbit){
return node;
}
if(nbit < pool_head->pool_link_num){
totalsize = (size_t)1 << nbit;
size = (size_t)1 << node->sbit;
node = node->near_next;
while((void*)node < pool->end && node->is_used == 0 && size < totalsize){
size += (size_t)1 << node->sbit;
node = node->near_next;
}
if(size >= totalsize){
//后面有足够的可用内存,合并内存,直接返回
MemNode *end_node = node;
_mem_del_node(&pool_head->used_link, used_node);
end_node = node;
node = used_node->near_next;
while(node < end_node){
_mem_del_node(pool->links + node->sbit, node);
node = node->near_next;
}
_mem_add_node(pool, &pool_head->used_link, used_node, nbit, 0, 1, 0, filename, lineno);
size -= totalsize;
node = used_node->near_next;
while(size){
nbit = up_power2(totalsize = size & (size ^ (size - 1)));
_mem_add_node(pool, pool->links + nbit, node, nbit, 0, 0, 0, NULL, 0);
size -= totalsize;
node = node->near_next;
}
return used_node;
}
}
//空间不够,申请空间,再释放(针对大内存块,同样适用)
node = _mem_alloc(pool_head, newsize, filename, lineno);
if(node == NULL){
return NULL;
}
memmove(node + 1, used_node + 1, realsize);
_mem_free(pool_head, used_node);
return node;
}
//程序退出时,清理内存池
static void _mem_exit(void){
mem_destroy(s_mempool);
}
//初始化内存池
static void _mem_init(){
if(s_mempool == NULL){
s_mempool = mpool_create(MEM_DEFAULT_POOL_SIZE);
assert(s_mempool != NULL);
atexit(_mem_exit);
}
}
//设置默认内存池的大小
void mpool_default_size(size_t size){
if(s_mempool == NULL){
s_mempool = mpool_create(size);
assert(s_mempool != NULL);
atexit(_mem_exit);
}
}
//设置或取得默认的内存池
void* mpool_default_pool(void *pool){
if(s_mempool == NULL && pool != NULL){
MemPoolHead *mem = (MemPoolHead*)pool;
if(mem->magic == MEM_MAGIC){
s_mempool = pool;
}else{
MemNode *node = (MemNode*)((char*)pool - sizeof(MemNode));
if(node->magic == MEM_MAGIC){
s_mempool = node->is_large ? (MemPoolHead*)node->pool : node->pool->head;
}
}
}
return s_mempool;
}
//分配内存
void* mpool_alloc_ex(void *h, size_t size, char *filename, int lineno, size_t *allocsize){
MemPoolHead *pool_head;
MemNode *node = NULL;
if(h == NULL){
_mem_init();
h = s_mempool;
}
pool_head = (MemPoolHead*)h;
assert(pool_head->magic == MEM_MAGIC);
#ifdef MEM_THREAD
pthread_mutex_lock(&pool_head->lock);
#endif
node = _mem_alloc(pool_head, size, filename, lineno);
if(node && allocsize){
*allocsize = node->data_len;
}
pool_head->malloc_count ++;
#ifdef MEM_THREAD
pthread_mutex_unlock(&pool_head->lock);
#endif
return node ? node + 1 : NULL;
}
//释放内存
void mpool_free_ex(void *p){
MemPoolHead *pool_head;
MemNode *node;
//验证结点是否正确
if(p == NULL){
return;
}
node = (MemNode*)((char*)p - sizeof(MemNode));
assert(node->magic == MEM_MAGIC && node->is_used == 1);
pool_head = node->is_large ? (MemPoolHead*)node->pool : node->pool->head;
assert(pool_head->magic == MEM_MAGIC);
#ifdef MEM_THREAD
pthread_mutex_lock(&pool_head->lock);
#endif
pool_head->free_count ++;
_mem_free(pool_head, node);
#ifdef MEM_THREAD
pthread_mutex_unlock(&pool_head->lock);
#endif
return;
}
//重新分配内存
void* mpool_realloc_ex(void *h, void *data, size_t size, char *filename, int lineno, size_t *allocsize){
MemPoolHead *pool_head;
MemNode *node;
//验证参数
if(data == NULL && size == 0){
return NULL;
}
if(data == NULL){
return mpool_alloc_ex(h, size, filename, lineno, allocsize);
}
if(size == 0){
mpool_free_ex(data);
return NULL;
}
//检查节点是否正确
node = (MemNode*)((char*)data - sizeof(MemNode));
if(node->magic != MEM_MAGIC || node->is_used == 0){
assert(0);
return NULL;
}
pool_head = node->is_large ? (MemPoolHead*)node->pool : node->pool->head;
assert(pool_head->magic == MEM_MAGIC);
#ifdef MEM_THREAD
pthread_mutex_lock(&pool_head->lock);
#endif
node = _mem_realloc(pool_head, node, size, filename, lineno);
if(node && allocsize){
*allocsize = node->data_len;
}
#ifdef MEM_THREAD
pthread_mutex_unlock(&pool_head->lock);
#endif
return node ? node + 1 : NULL;
}
//输出已分配内存的信息
size_t mpool_dump(void *h, char *out, size_t maxlen){
MemPoolHead *pool_head;
MemNode *node;
DLink *links[2];
size_t
len = 0, total = 0;
int i;
if(h == NULL){
if(s_mempool == NULL){
return 0;
}
h = s_mempool;
}
pool_head = (MemPoolHead*)h;
assert(pool_head->magic == MEM_MAGIC);
links[0] = &pool_head->used_link;
links[1] = &pool_head->large_link;
#ifdef MEM_THREAD
pthread_mutex_lock(&pool_head->lock);
#endif
for(i = 0; i < 2; i ++){
node = links[i]->head;
while(node){
total += node->data_len;
if(out == NULL){
if(node->filename_len){
len += fprintf(stderr, "0x%p\t%ld\t%s:%d\n", node + 1, node->data_len, (char*)node + sizeof(MemNode) + node->data_len, node->lineno);
}else{
len += fprintf(stderr, "0x%p\t%ld\n", node + 1, node->data_len);
}
}else{
if(len >= maxlen){
break;
}
if(node->filename_len){
len += snprintf(out + len, maxlen - len, "0x%p\t%ld\t%s:%d\n", node + 1, node->data_len, (char*)node + sizeof(MemNode) + node->data_len, node->lineno);
}else{
len += snprintf(out + len, maxlen - len, "0x%p\t%ld\n", node + 1, node->data_len);
}
}
node = node->next;
}
}
len += snprintf(out + len, maxlen - len, "total:%ld\n", total);
#ifdef MEM_THREAD
pthread_mutex_unlock(&pool_head->lock);
#endif
return len;
}
//输出已分配内存的信息
void mpool_check(void *h){
MemPoolHead *pool_head;
MemNode *node;
DLink *links[2];
int i;
if(h == NULL){
if(s_mempool == NULL){
return;
}
h = s_mempool;
}
pool_head = (MemPoolHead*)h;
assert(pool_head->magic == MEM_MAGIC);
links[0] = &pool_head->used_link;
links[1] = &pool_head->large_link;
#ifdef MEM_THREAD
pthread_mutex_lock(&pool_head->lock);
#endif
for(i = 0; i < 2; i ++){
node = links[i]->head;
while(node){
assert(node->magic == MEM_MAGIC);
assert(node->is_used == 1);
node = node->next;
}
}
#ifdef MEM_THREAD
pthread_mutex_unlock(&pool_head->lock);
#endif
}
#ifdef SELF_TEST
#define TEST_COUNT
1000000
#define TEST_NUM
10000
#define MEM_SIZE
rand() % 4000
#define my_malloc(n) mem_alloc_ex(NULL, n, __FILE__, __LINE__, NULL)
#define my_free(p) mem_free_ex(p)
#define my_realloc(p, n) mem_realloc_ex(NULL, p, n, __FILE__, __LINE__, NULL)
int test(int flag){
int j, s1, s2, num;
size_t idx;
void *p[TEST_NUM] = {0};
num = 0;
s1 = clock();
for(j = 0; j < TEST_COUNT; j ++){
idx = rand() % TEST_NUM;
if(p[idx] == NULL){
if(flag == 1){
p[idx] = my_malloc(MEM_SIZE);
}else{
p[idx] = malloc(MEM_SIZE);
}
num ++;
}else{
if(rand() % 2 == 0){
if(flag == 1){
p[idx] = my_realloc(p[idx], MEM_SIZE);
}else{
p[idx] = realloc(p[idx], MEM_SIZE);
}
}else{
if(flag == 1){
my_free(p[idx]);
}else{
free(p[idx]);
}
p[idx] = NULL;
num --;
}
}
}
s2 = clock();
return s2 - s1;
}
int main(){
srand((int)time(NULL));
printf("%d\n", test(1));
printf("%d\n", test(0));
return 0;
}
#endif
mempool.h
#ifndef _MEM_POOL_H
#define _MEM_POOL_H
//创建内存池,mem_size内存池的大小
//内存池的实际大小是mem_size向上取整,2的幂次
//调用mem_create函数,不会分配空间,在第一次调用mem_alloc_ex时分配
//最小是1M,小于1M的,自动变成1M
void* mpool_create(size_t mem_size);
//删除内存池
void mpool_destroy(void *h);
//设置默认内存池的大小
void mpool_default_size(size_t size);
//设置或得到默认的内存池(如果pool为空,取得当前的内存池,如果不为空,设置默认内存池)
void* mpool_default_pool(void *pool);
//分配内存
//allocsize为实际可用的内存大小,一般分配的内存是>=size的
//每次分配内存都是按2的幂次来分配的,由于还有头结构以及其它信息,实际可用大小并不是2的幂次
//如果h传入的是NULL,程序会使用默认的内存池,静态变量保存(如果是在so中,需要保证malloc和free次数一致,不允许so内malloc,外部free)
void* mpool_alloc_ex(void *h, size_t size, char *filename, int lineno, size_t *allocsize);
//释放内存
void mpool_free_ex(void *p);
//重新分配内存
//data==NULL 分配内存
//size==0
//size>原始大小检查后面是否有空余内存,有则直接返回,没有先malloc,再memmove,然后free
//size<原始大小 将内存分裂,后面的变为可用内存,供以后内存分配
//allocsize 为实际可用的内存大小,>=size
void* mpool_realloc_ex(void *h, void *data, size_t size, char *filename, int lineno, size_t *allocsize);
//dump内存池中所有已分配的内存,输出到out中,最大长度maxlen
//如果out为NULL,输出到stderr
//如果有文件名,输出文件名和行号
//如果h为NULL,会dump默认的内存池
size_t mpool_dump(void *h, char *out, size_t maxlen);
//检查内存池,是否有越界产生
//如果出错,程序直接退出 assert操作
//如果h为NULL,检查默认的内存池
void mpool_check(void *h);
//向上取整,返回的是位数,需要1<<得到真实的数字
char up_power2(size_t m);
//使用时为了方便,可以定义如下的宏
//整个程序的运行,只需要一个内存池就可以
#define malloc(size)
#define realloc(p,size)
#define free(p)
#endif
源代码文件:mempool.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
#include <assert.h>
#include <pthread.h>
#ifdef WIN32
#define snprintf _snprintf
#endif
#define IS_64BIT
#define MEM_THREAD
//内存魔数
#define MEM_MAGIC
#define MEM_LARGE_SIZE
#define MEM_DEFAULT_POOL_SIZE
#define MEM_MERGE_PER_FREE
typedef struct MemPoolHead MemPoolHead;
typedef struct MemPool MemPool;
typedef struct MemNode MemNode;
//双链表 头结点和尾节点
typedef struct{
}DLink;
//内存节点
struct MemNode{
};
//内存池的头结点
struct MemPoolHead{
#ifdef MEM_THREAD
#endif
};
//内存池
struct MemPool{
};
static void *s_mempool;
//向上取整,得到最小的2的幂次
char up_power2(size_t m) {
}
//添加一个节点 0-释放内存,1-分配内存 2-大内存
static void _mem_add_node(MemPool *pool, DLink *link, MemNode *node, int sizebit, size_t realsize, int is_used, int is_large, char *filename, int lineno){
}
//从双链表中删除一个节点
static void _mem_del_node(DLink *link, MemNode *node){
}
//删除内存池(释放所有内存)
void mem_destroy(void *h){
}
//删除一个pool
static void _mem_del(MemPoolHead *pool_head, MemPool *pool){
}
//新建一个pool
MemPool* _mem_create(MemPoolHead *pool_head){
}
//初始化一个内存池
void* mpool_create(size_t pool_size){
#ifdef MEM_THREAD
#endif
}
//将一块内存分裂出一块来分配空间
static void _mem_split(MemPool *pool, MemNode *node, int totalbit, int sbit, char *filename, int lineno){
}
//分配大块内存
static MemNode* _mem_large(MemPoolHead *pool_head, size_t realsize, char *filename, int lineno){
}
//按照大小排序
static int _compare_point(const void *a, const void *b){
}
//合并内存碎片
static int _mem_merge(MemPool *pool, int sizebit){
}
//分配内存
static MemNode* _mem_alloc(MemPoolHead *pool_head, size_t size, char *filename, int lineno){
}
//释放一个结点
static void _mem_free(MemPoolHead *pool_head, MemNode *node){
}
//重新分配内存
static MemNode* _mem_realloc(MemPoolHead *pool_head, MemNode *node, size_t newsize, char *filename, int lineno){
}
//程序退出时,清理内存池
static void _mem_exit(void){
}
//初始化内存池
static void _mem_init(){
}
//设置默认内存池的大小
void mpool_default_size(size_t size){
}
//设置或取得默认的内存池
void* mpool_default_pool(void *pool){
}
//分配内存
void* mpool_alloc_ex(void *h, size_t size, char *filename, int lineno, size_t *allocsize){
#ifdef MEM_THREAD
#endif
#ifdef MEM_THREAD
#endif
}
//释放内存
void mpool_free_ex(void *p){
#ifdef MEM_THREAD
#endif
#ifdef MEM_THREAD
#endif
}
//重新分配内存
void* mpool_realloc_ex(void *h, void *data, size_t size, char *filename, int lineno, size_t *allocsize){
#ifdef MEM_THREAD
#endif
#ifdef MEM_THREAD
#endif
}
//输出已分配内存的信息
size_t mpool_dump(void *h, char *out, size_t maxlen){
#ifdef MEM_THREAD
#endif
#ifdef MEM_THREAD
#endif
}
//输出已分配内存的信息
void mpool_check(void *h){
#ifdef MEM_THREAD
#endif
#ifdef MEM_THREAD
#endif
}
#ifdef SELF_TEST
#define TEST_COUNT
#define TEST_NUM
#define MEM_SIZE
#define my_malloc(n) mem_alloc_ex(NULL, n, __FILE__, __LINE__, NULL)
#define my_free(p) mem_free_ex(p)
#define my_realloc(p, n) mem_realloc_ex(NULL, p, n, __FILE__, __LINE__, NULL)
int test(int flag){
}
int main(){
}
#endif
相对于在栈空间分配内存,堆中分配内存其实是非常缓慢的。
另外,由于堆中分配的内存,需要开发者编码回收,当系统非常庞大时,容易出现分配的内容没有回收导致内存泄露的现象。
因此,许多Bible建议开发者尽量使用栈空间,少用甚至不用malloc和free、new和delete;
虽然栈的空间较小,但这样的建议随着计算机的位数从32位升级到64位,越来越成为真理。
但我还是想说,这是有限制的:那就是这条真理适用于多线程程序,但在多协程程序中,由于协程栈空间的限制,极容易撑爆协程栈。
为了提高堆分配内存的速度,内存池出现了。
下面提供固定大小的内存池和可变大小的内存池实现。经过测试,它的性能远高于Boost的内存池哦!
基础头文件base.h
- #ifndef __PP_BASE_H__
- #define __PP_BASE_H__
- #if (defined(WIN32) || defined(WIN64))
- #ifndef WINVER // Specifies that the minimum required platform is Windows Vista.
- #define WINVER 0x0600 // Change this to the appropriate value to target other versions of Windows.
- #endif
- #ifndef _WIN32_WINNT // Specifies that the minimum required platform is Windows Vista.
- #define _WIN32_WINNT 0x0600 // Change this to the appropriate value to target other versions of Windows.
- #endif
- #ifndef _WIN32_WINDOWS // Specifies that the minimum required platform is Windows 98.
- #define _WIN32_WINDOWS 0x0410 // Change this to the appropriate value to target Windows Me or later.
- #endif
- #ifndef _WIN32_IE // Specifies that the minimum required platform is Internet Explorer 7.0.
- #define _WIN32_IE 0x0700 // Change this to the appropriate value to target other versions of IE.
- #endif
- //
- #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
- // Windows Header Files:
- #include <winsock2.h>
- #include <windows.h>
- #ifndef PPAPI
- #define PPAPI __stdcall
- #endif
- #else
- #ifndef PPAPI
- #define PPAPI
- #endif
- #endif
- #endif
内存池头文件
- #ifndef __PPMEMORYPOOL_H__
- #define __PPMEMORYPOOL_H__
- #include "ppbase.h"
- /**
- * @brief 可变内存块大小内存池,
- * 内存池申请的内存不会自动释放,只在内存池销毁时释放。
- * 注意:此类“非”线程安全
- * 使用示例:
- * CVarMemoryPool pool;
- * pool.Create();
- * 某一线程:
- * char * p1 = pool.Malloc(512);//记得加锁
- * char * p2 = pool.Malloc(31);//记得加锁
- * char * p3 = pool.Malloc(128);//记得加锁
- *
- * 另外一个线程:
- * pool.Free(p3);//记得加锁
- * pool.Free(p2);//记得加锁
- * pool.Free(p1);//记得加锁
- */
- class CVarMemoryPool
- {
- struct MemoryPage
- {
- MemoryPage* Next; // next memory page
- };
- public:
- CVarMemoryPool();
- ~CVarMemoryPool();
- /**
- * @brief
- * 创建可变内存池
- * @param dwPageSize : 内部分配的内存页大小,内存不够时,内存池会申请一块新的内存页
- * @return 创建成功返回TRUE,否则返回FALSE
- **/
- bool PPAPI Create(unsigned int dwPageSize = 0x80000);
- /**
- * @brief
- * 分配Len长度的Buffer
- * @param dwLen : 获得的内存块大小长度
- * @return 返回的内存,如果返回为NULL,则代表分配失败
- **/
- void* PPAPI Malloc(unsigned int dwLen);
- /**
- * @brief
- * 回收内存
- * @param p : 指向需要回收的内存
- * @return void
- **/
- void PPAPI Free(void* p);
- /**
- * @brief
- * 清空内存池,使所有的内存都可以使用,此方法不会将内存返回给操作系统
- * @return void
- **/
- void PPAPI Clear();
- /**
- * @brief
- * 获取当前内存使用量
- * @return 当前内存使用量
- **/
- int PPAPI GetMemUsed();
- private:
- void* GetPoolMemory(unsigned int dwLen);
- void FreePoolMemory(void* pMemBlock, unsigned char dwType);
- bool AddFreeMemory(int dwIndex);
- bool SetMemoryPage();
- inline char* GetPageBufGegin(MemoryPage *pPage)
- {
- return (char*)(pPage + 1);
- }
- inline char* GetPageBufEnd(MemoryPage *pPage)
- {
- return (char*)(pPage + 1) + m_nPageSize;
- }
- private:
- static const unsigned int ALIGNMENT = 8;
- static const unsigned int ALLOC_COUNT = 16;
- static const unsigned int MIN_PAGESIZE = 0x40000; // min pPage size
- static const unsigned int MAX_UNIT_SIZE = 128;
- static const unsigned int UNIT_TYPE_COUNT = 16;
- char* m_pFreeHead[UNIT_TYPE_COUNT];
- int m_nFreeCount[UNIT_TYPE_COUNT];
- MemoryPage* m_pHeadPage;
- MemoryPage* m_pWorkPage;
- char* m_pPageBuf;
- unsigned int m_nPageSize;
- };
- /**
- *@brief 固定内存块大小内存池,用于分配固定大小的内存块
- * 内存池申请的内存不会自动释放,只在内存池销毁时释放
- * 注意:此类“非”线程安全
- * 使用示例:
- * CFixMemoryPool pool;
- * pool.Create(128);
- * 某一线程:
- * char * p1 = pool.Malloc();//记得加锁
- * char * p2 = pool.Malloc();//记得加锁
- * char * p3 = pool.Malloc();//记得加锁
- *
- * 另外一个线程:
- * pool.Free(p3);//记得加锁
- * pool.Free(p2);//记得加锁
- * pool.Free(p1);//记得加锁
- */
- class CFixMemoryPool
- {
- struct MemoryPage
- {
- MemoryPage* Next; // next memory page
- int nFreeHead; // the first free unit in page
- int nFreecount; // free unit in page
- };
- public:
- CFixMemoryPool();
- ~CFixMemoryPool();
- /**
- * @brief
- * 初始化内存池
- * @param dwUnitSize : 每一个分配的内存块大小
- * @param dwPageSize : 内部分配的内存页大小,内存不够时,内存池会申请一块新的内存页
- * @return 创建成功返回true,失败返回false
- **/
- bool PPAPI Create(unsigned int dwUnitSize, unsigned int dwPageSize = 0x40000);
- /**
- * @brief
- * 得到一块新的内存
- * @return void*
- **/
- void* PPAPI Malloc();
- /**
- * @brief
- * 归还一块分配的内存
- * @param p : 内存的地址
- * @return void
- **/
- void PPAPI Free(void* p);
- /**
- * @brief
- * 清空内存池,使所有的内存都可以使用,此方法不会将内存返回给操作系统
- * @return void
- **/
- void PPAPI Clear();
- /**
- * @brief
- * 获取当前内存使用量
- * @return 当前内存使用量
- **/
- int PPAPI GetMemUsed();
- private:
- bool AddMemoryPage();
- void InitPage(MemoryPage *pPage);
- inline char* GetPageBuf(MemoryPage *pPage)
- {
- return (char*)(pPage + 1);
- }
- private:
- static const int ALIGNMENT = 4;
- static const unsigned int MIN_PAGESIZE = 0x40000; // min page size
- MemoryPage* m_pHeadPage; // first page
- unsigned int m_nUnitSize; // unit memory size
- unsigned int m_nPageSize; // total memory in page
- };
- #endif
内存池cpp文件
- #include "memorypool.h"
- #include <assert.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <malloc.h>
- CVarMemoryPool::CVarMemoryPool()
- :m_pHeadPage(NULL), m_pWorkPage(NULL), m_pPageBuf(NULL)
- {
- for (unsigned int i = 0; i < UNIT_TYPE_COUNT; ++ i)
- {
- m_pFreeHead[i] = NULL;
- m_nFreeCount[i] = 0;
- }
- }
- CVarMemoryPool::~CVarMemoryPool()
- {
- MemoryPage* pMemoryPage = m_pHeadPage;
- while (m_pHeadPage != NULL)
- {
- pMemoryPage = m_pHeadPage->Next;
- free(m_pHeadPage);
- m_pHeadPage = pMemoryPage;
- }
- }
- void* CVarMemoryPool::Malloc(unsigned int Len)
- {
- assert(Len > 0);
- Len ++;
- if (Len > MAX_UNIT_SIZE)
- {
- // allocate memory from system if requery Len is too large
- void* buf = malloc(Len);
- if (buf == NULL)
- {
- return NULL;
- }
- //if content of 1 byte before memory means allocate form system
- *(char*)buf = 0;
- return (char*)buf + 1;
- }
- else
- {
- return GetPoolMemory(Len);
- }
- }
- void CVarMemoryPool::Free(void* p)
- {
- assert(p != NULL);
- char* temp = (char*)p - 1;
- unsigned char type = *temp;
- if (type == 0) //if content of 1 byte before memory means allocate form system
- {
- free(temp);
- }
- else
- {
- FreePoolMemory(temp, type);
- }
- }
- void* CVarMemoryPool::GetPoolMemory(unsigned int Len)
- {
- Len = (Len + (ALIGNMENT-1)) & ~(ALIGNMENT-1);
- int idx = (Len - 1) / ALIGNMENT;
- //if free memory unit is not enough, first get some free units
- if (m_nFreeCount[idx] == 0
- && !AddFreeMemory(idx))
- {
- return NULL;
- }
- -- m_nFreeCount[idx];
- char* buf = m_pFreeHead[idx];
- m_pFreeHead[idx] = (char*)(*((INT64*)m_pFreeHead[idx]));
- *buf = idx + 1;
- return buf + 1;
- }
- void CVarMemoryPool::FreePoolMemory(void* memblock, unsigned char type)
- {
- int idx = type - 1;
- *(INT64*)memblock = (INT64)m_pFreeHead[idx];
- m_pFreeHead[idx] = (char*)memblock;
- ++ m_nFreeCount[idx];
- }
- bool CVarMemoryPool::AddFreeMemory(int idx)
- {
- const int UNIT_SIZE = (idx + 1) * ALIGNMENT;
- if ((m_pPageBuf + UNIT_SIZE ) > GetPageBufEnd(m_pWorkPage)
- && !SetMemoryPage())
- {
- return false;
- }
- char* page_end = GetPageBufEnd(m_pWorkPage);
- for (unsigned int i = 0; i < ALLOC_COUNT; ++ i)
- {
- *(INT64*)m_pPageBuf = (INT64)m_pFreeHead[idx];
- m_pFreeHead[idx] = m_pPageBuf;
- m_pPageBuf += UNIT_SIZE;
- ++ m_nFreeCount[idx];
- if (m_pPageBuf + UNIT_SIZE > page_end)
- break;
- }
- return true;
- }
- bool CVarMemoryPool::SetMemoryPage()
- {
- if(m_pWorkPage->Next != NULL)
- {
- m_pWorkPage = m_pWorkPage->Next;
- }
- else
- {
- void* buf = malloc(sizeof(MemoryPage) + m_nPageSize);
- if (buf == NULL)
- {
- return false;
- }
- else
- {
- MemoryPage* pMemoryPage = (MemoryPage*)(buf);
- pMemoryPage->Next = NULL;
- m_pWorkPage->Next = pMemoryPage;
- m_pWorkPage = pMemoryPage;
- }
- }
- m_pPageBuf = GetPageBufGegin(m_pWorkPage);
- return true;
- }
- int CVarMemoryPool::GetMemUsed()
- {
- int used = 0;
- const int PAGE_SIZE = sizeof(MemoryPage) + m_nPageSize;
- MemoryPage* pMemoryPage = m_pHeadPage;
- while (pMemoryPage != NULL)
- {
- pMemoryPage = pMemoryPage->Next;
- used += PAGE_SIZE;
- }
- return used;
- }
- void CVarMemoryPool::Clear()
- {
- m_pWorkPage = m_pHeadPage;
- m_pPageBuf = GetPageBufGegin(m_pWorkPage);
- }
- bool CVarMemoryPool::Create( unsigned int PageSize /*= 0x80000*/ )
- {
- PageSize = (PageSize + (ALIGNMENT-1)) & ~(ALIGNMENT-1);
- if (PageSize < MIN_PAGESIZE)
- {
- m_nPageSize = MIN_PAGESIZE;
- }
- else
- {
- m_nPageSize = PageSize;
- }
- void* buf = malloc(sizeof(MemoryPage) + m_nPageSize);
- if (buf == NULL)
- {
- return false;
- }
- else
- {
- MemoryPage* pMemoryPage = (MemoryPage*)(buf);
- pMemoryPage->Next = NULL;
- m_pWorkPage = pMemoryPage;
- m_pPageBuf = GetPageBufGegin(m_pWorkPage);
- m_pHeadPage = m_pWorkPage;
- }
- return true;
- }
- //
- CFixMemoryPool::CFixMemoryPool()
- :m_pHeadPage(NULL), m_nUnitSize(0), m_nPageSize(0)
- {
- }
- CFixMemoryPool::~CFixMemoryPool()
- {
- MemoryPage* pMemoryPage = m_pHeadPage;
- while (m_pHeadPage != NULL)
- {
- pMemoryPage = m_pHeadPage->Next;
- free(m_pHeadPage);
- m_pHeadPage = pMemoryPage;
- }
- }
- void* CFixMemoryPool::Malloc()
- {
- MemoryPage* pMemoryPage = m_pHeadPage;
- while (pMemoryPage != NULL && pMemoryPage->nFreecount == 0)
- {
- pMemoryPage = pMemoryPage->Next;
- }
- // add new page if space is not enough
- if (pMemoryPage == NULL)
- {
- if(!AddMemoryPage())
- {
- return NULL;
- }
- pMemoryPage = m_pHeadPage;
- }
- // get unused memory
- -- pMemoryPage->nFreecount;
- char* buf = GetPageBuf(pMemoryPage) + pMemoryPage->nFreeHead * m_nUnitSize;
- pMemoryPage->nFreeHead = *(int*)(buf);
- return buf;
- }
- void CFixMemoryPool::Free(void* p)
- {
- // don't check null point for fast
- MemoryPage* pMemoryPage = m_pHeadPage;
- char* buf = GetPageBuf(m_pHeadPage);
- // find point in which page
- while((p < buf ||
- p > buf + m_nPageSize) &&
- pMemoryPage != NULL)
- {
- pMemoryPage = pMemoryPage->Next;
- buf = GetPageBuf(pMemoryPage);
- }
- // do not in any page
- if (pMemoryPage == NULL)
- {
- return;
- }
- *(int*)p = pMemoryPage->nFreeHead;
- pMemoryPage->nFreeHead = ((char*)p - buf) / m_nUnitSize;
- ++ pMemoryPage->nFreecount;
- return;
- }
- bool CFixMemoryPool::AddMemoryPage()
- {
- void* buf = malloc(sizeof(MemoryPage) + m_nPageSize);
- if (buf == NULL)
- {
- return false;
- }
- MemoryPage* pMemoryPage = (MemoryPage*)(buf);
- InitPage(pMemoryPage);
- if (m_pHeadPage == NULL)
- {
- pMemoryPage->Next = NULL;
- m_pHeadPage = pMemoryPage;
- }
- else
- {
- pMemoryPage->Next = m_pHeadPage;
- m_pHeadPage = pMemoryPage;
- }
- return true;
- }
- int CFixMemoryPool::GetMemUsed()
- {
- int used = 0;
- const int PAGE_SIZE = sizeof(MemoryPage) + m_nPageSize;
- MemoryPage* pMemoryPage = m_pHeadPage;
- while (pMemoryPage != NULL)
- {
- pMemoryPage = pMemoryPage->Next;
- used += PAGE_SIZE;
- }
- return used;
- }
- void CFixMemoryPool::Clear()
- {
- MemoryPage* pMemoryPage = m_pHeadPage;
- while (pMemoryPage != NULL)
- {
- InitPage(pMemoryPage);
- pMemoryPage = pMemoryPage->Next;
- }
- }
- void CFixMemoryPool::InitPage(MemoryPage *Page)
- {
- Page->nFreecount = m_nPageSize / m_nUnitSize;
- Page->nFreeHead = 0;
- void* head = GetPageBuf(Page);
- for (int i = 1; i < Page->nFreecount; ++i)
- {
- *(int*)head = i;
- head = (int*)((char*)head + m_nUnitSize);
- }
- }
- bool CFixMemoryPool::Create( unsigned int UnitSize, unsigned int PageSize /*= 0x40000*/ )
- {
- if (UnitSize < 4)
- {
- m_nUnitSize = 4;
- }
- {
- m_nUnitSize = (UnitSize + (ALIGNMENT-1)) & ~(ALIGNMENT-1);
- }
- if (PageSize < MIN_PAGESIZE)
- {
- m_nPageSize = MIN_PAGESIZE;
- }
- else
- {
- m_nPageSize = (PageSize / m_nUnitSize) * m_nUnitSize;
- }
- return AddMemoryPage();
- }