内存池

来源:互联网 发布:mysql 删除表怎么恢复 编辑:程序博客网 时间:2024/05/19 14:01

链表是大家非常熟悉的数据结构,使用频率也非常高,但是链表有几个缺点。首先,我们每创建一个节点,都要进行一下系统调用分配一块空间,这会浪费一点时间;其次,由于创建节点的时间不固定,会导致节点分配的空间不连续,容易形成离散的内存碎片;最后,由于内存不连续,所以链表的局部访问性较差,容易出现cache缺失。 
针对链表的上述问题,在实际工作中,我们很少直接用链表,而是采用链表的替代品—内存池。上过操作系统课程的同学对内存池应该不陌生,而且应该也知道设计一个好的内存池非常麻烦。但替换链表的内存池做了很大的简化:它是单线程的而且是只支持固定块大小的内存池。在具体实现过程中,我们先分配N块固定大小的连续内存,N块需要根据需求设定,之后每需要一个节点就从内存池中get一块空闲的块,用完之后再回收到内存池中。 
内存池可以解决链表三个问题中的前两个,不能解决后一个,但是如果内存池较小可以缓解cache缺失的问题,整体而言还是可以很好地代替链表。下面看一下具体实现。

1. 结构体

内存池想象中比较简单,就是首先分配一块大内存,每次取一小块内存,用完再放回去即可,但是实现起来需要较多的辅助变量。我们不能仅仅通过一个指针来完成对内存池的访问,因为获取和释放的顺序是随机的。我们需要标记每一块的使用状况。内存池的结构体如下:

typedef struct _mem_pool_{    char*  buffer_arr;    char** index_arr;    char** index_cur;    char** index_end;}mem_pool_t;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

buffer_arr就是原始的整块大内存,除了这个变量之外还有三个二维指针:index_arr是一个指针数组,分别指向每一块的首地址,index_cur是一个遍历指针,用来指向当前可分配的块,index_end表示可分配块的末尾。

2. 初始化

初始化需要指定块大小和块个数,然后分配大块内存。但初始化的关键操作是初始化几个二维指针。看一下代码:

/** * @brief 创建内存池 * @param capacity   容量 * @param block_size 对象单元大小 * @return 内存池对象指针,如果创建失败返回NULL */mem_pool_t* mem_pool_init(int capacity, int unit_size){    int i = 0;    char *work = NULL;    mem_pool_t* mem_pool = NULL;    if(capacity <=0 || unit_size <=0)    {        printf("Illegal params, capacity[%d]"                    " unit_size[%d]", capacity, unit_size);        return NULL;    }    mem_pool = (mem_pool_t*)malloc(sizeof(mem_pool_t));    if(NULL == mem_pool)    {        printf("init memery pool failed");        return NULL;    }    mem_pool->buffer_arr = (char*)malloc(capacity*unit_size);    if(NULL == mem_pool->buffer_arr)    {        printf("Failed to alloc mem_pool buffer");        return NULL;    }    mem_pool->index_arr = (char**)malloc(sizeof(char*)*capacity);    if(NULL == mem_pool->index_arr)    {        printf("Failed to alloc memory pool "                    "index array");        return NULL;    }    work = mem_pool->buffer_arr;    for(i=0; i<capacity; i++, work+=unit_size)    {        mem_pool->index_arr[i] = work;    }    mem_pool->index_end = mem_pool->index_arr + capacity;    mem_pool->index_cur = mem_pool->index_arr;    return mem_pool;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

index_arr是一个和内存池容量一样的二维指针,它被初始化为内存池每一块的首地址。index_end指向index_arr末尾的下一个位置,用来表示内存池的末尾。index_cur只是简单地等于index_arr的首地址。后面我们就可以通过加减index_cur来分配或回收一块内存。

3. get函数

get函数的目的是从内存池中取一块可用内存,它首先判断是否还有可用块,如果有就返回当前可用块。返回可用块的方法很简单,只需要将index_cur对应位置的块返回即可。

/** * @brief 从内存池中分配一个单元 * @param mem_pool 内存池指针 * @return 新分配的对象指针,如果分配失败返回NULL */void* mem_pool_alloc(mem_pool_t* mem_pool){    void* ret;    if(mem_pool->index_cur >= mem_pool->index_end)    {        printf("memory pool overflow");        return NULL;    }    ret = *(mem_pool->index_cur++);    return ret;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

4. free函数

free函数的目的是回收一块用完的内存。和get相反,我们只需要把回收内存的地址赋给index_cur-1即可。

/** * @brief 内存池回收一个对象单元 * @param mem_pool 内存池 * @param obj    待回收的对象单元 * @return errno *         0  : OK *         -1 : ERROR */int mem_pool_free(mem_pool_t* mem_pool, void* obj){    if(NULL == mem_pool)    {        printf("try to free block in NULL pool");        return MEM_POOL_ERR;    }    if(NULL == obj)    {        printf("try to free NULL object");        return MEM_POOL_ERR;    }    *(--mem_pool->index_cur) = (char*)obj;    return MEM_POOL_OK;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

这里需要注意的是,index_arr一开始是顺序指向每一块内存的,但是在不停地get和free过程中index_arr开始乱序指向每一块内存。 
可以看出,上面的get和free操作都非常简单,只是简单的加减操作,所以速度非常快,而且内存池是一整块内存,不存在内存碎片的问题。同时,如果内存池较小,也可以很大程度上缓解cache缺失问题。