您的位置:首页 > 其它

浅谈内存池几种设计方式

2012-07-19 11:50 323 查看
写服务器的,通常会涉及到内存池的东西,自己在这方面也看了写了一些东西,有些体会,写出来跟大家分享下。

内存池基本包含以下几个东西,第一,初始化。第二,分配内存。第三,回收内存。所谓初始化,就是在服务器启动的时候,或者第一次需要内存的时候,系统分配很大的一块内存,方便之后的使用。分配内存,就是从内存池中取出需要的内存给外部使用,当然这里需要考虑的是当内存池中没有内存可分配时候的处理。回收内存,简单来说,就是外面对象生命期结束了,将分配出去的内存回收入内存池中。好了简单概念就说完了,我们先来看一种最简单的设计方式。

//为了方便描述,这里附上几个简单的链表操作宏

#define INSERT_TO_LIST( head, item, prev, next )
\
do{ \
if ( head )
\
(head)->prev = (item);
\
(item)->next = (head);
\
(head) = (item); \
}while(0)

#define REMOVE_FROM_LIST(head, item, prev, next)
\
do{ \
if ( (head) == (item) )
\
{
\
(head) = (item)->next;
\
if ( head )
\
(head)->prev = NULL;
\
}
\
else
\
{
\
if ( (item)->prev )
\
(item)->prev->next = (item)->next; \
\
if ( (item)->next )
\
(item)->next->prev = (item)->prev; \
}
\
}while(0)

struct student

{

char name[32];

byte sex;

struct student *prev,*next;

};

static struct mem_pool

{

//该指针用来记录空闲节点

struct student *free;

//该变量记录分配结点个数

size_t alloc_cnt;

}s_mem_pool;

//分配内存“块”的函数

bool mem_pool_resize(size_t size)

{

//该函数创建size个不连续的对象,把他们通过链表的方式加入到s_mem_pool.free中

for ( size_t i = 0;i < size;++i )

{

struct student *p = (struct student *)malloc(sizeof(struct student));

if ( !p )

return false;

p->prev = p->next = NULL;

INSERT_TO_LIST(s_mem_pool.free,p,prev,next);

}

s_mem_pool.alloc_cnt += size;

}

#define MEM_INIT_SIZE 512
#define MEM_INC_SIZE 256
//初始化函数
bool mem_pool_init()
{
if ( !mem_pool_resize(MEM_INIT_SIZE) )
return false;

return true;
}

struct student *get_data()
{
if ( s_mem_pool.free == NULL )
{
if ( !mem_pool_resize(MEM_INC_SIZE) )
return NULL;
}

struct student *ret = s_mem_pool.free;
REMOVE_FROM_LIST(s_mem_pool.free,ret,prev,next)
return ret;
}

void free_data(struct student *p)
{
if ( !p )
return;

memset(p,0,sizeof(struct student));
INSERT_TO_LIST(s_mem_pool.free,p,prev,next)
}

好了最简单的内存池的大致框架就是这样。我们先来看下他的过程。首先,在mem_pool_init()函数中,他先分配512个不连续的student对象。每分配出来一个就把它加入到free链表中,初始化完成后内存池大概是这样的



接下来就是从内存池中取出一个对象get_data()。函数先去判断是否有空闲的对象,有则直接分配,否则再向系统获取一"块"大的内存。调用一次后的内存池大概是这样的



释放对象,再把对象加入到Free链表中。

以上就是过程的简单分析,下面我们来看看他的缺点。

第一,内存不是连续的,容易产生碎片

第二,一个类型就得写一个这样的内存池,很麻烦

第三,为了构建这个内存池,每个没对象必须加上一个prev,next指针

好了,我们来优化一下它。我们重新定义下我们的结构体

union student

{

int index;

struct

{

char name[32];

byte sex;

}s;

};

static struct mem_pool

{

//该下标用来记录空闲节点

int free;

//内存池

union student *mem;

//已分配结点个数

size_t alloc_cnt;

}s_mem_pool;

//分配内存块的函数

bool mem_pool_resize(size_t size)

{

size_t new_size = s_mem_pool.alloc_cnt+size;

union student *tmp = (union student *)realloc(s_mem_pool.mem,new_size*sizeof(union student));

if ( !tmp )

return false;

memset(tmp+s_mem_pool.alloc_cnt,0,size*sizeof(union student));

size_t i = s_mem_pool.alloc_cnt;

for ( ;i < new_size - 1;++i )

{

tmp[i].index = i + 1;

}

tmp[i].index = -1;

s_mem_pool.free = s_mem_pool.alloc_cnt;

s_mem_pool.mem = tmp;

s_mem_pool.alloc_cnt = new_size;

return true;

}

#define MEM_INIT_SIZE 512

#define MEM_INC_SIZE 256

//初始化函数

bool mem_pool_init()

{

if ( !mem_pool_resize(MEM_INIT_SIZE) )

return false;

return true;

}

union student *get_data()

{

if ( s_mem_pool.free == -1 )

{

if ( !mem_pool_resize(MEM_INC_SIZE) )

return NULL;

}

union student *ret = s_mem_pool.mem+s_mem_pool.free;

s_mem_pool.free = ret->index;

return ret;

}

void free_data(union student *p)

{

if ( !p )

return;

p->index = s_mem_pool.free;

s_mem_pool.free = p - s_mem_pool.mem;

}
我们来看看改进了些什么。第一student改成了联合体,这主要是为了不占用额外的内存,也就是我们上面所说的第三个缺点,第二,我们使用了realloc函数,这样我们可以使我们分配出来的内存是连续的。我们初始化的时候多了一个for循环,这是为了记录空闲对象的下标,当我们取出一个对象时,free可以立刻知道下一个空闲对象的位置,释放的时候,对象先记录free此时的值,接着再把free赋值成该对象在数组的下标,这样就完成了回收工作。

我们继续分析这段代码,问题在realloc函数上,如果我们的s_mem_pool.mem已经很大了,在realloc的时候我们都知道,先要把原来的数据做一次拷贝,所以如果数据量很大的情况下做一次拷贝,是会消耗性能的。那这里有没有好的办法呢,我们进一步优化

思路大概是这样

初始化



再次分配的时候,我们只需要重新分配新的内存单元,而不需要拷贝之前的内存单元。



因此基于此思路,我们修改我们的代码

#include <stdio.h>

#include <stdlib.h>

struct student

{

int index;

char name[32];

byte sex;

};

static struct mem_pool

{

//该下标用来记录空闲节点

int free;

//内存池

struct student **mem;

//已分配块个数

size_t block_cnt;

}s_mem_pool;

#define BLOCK_SIZE 256 //每块的大小

//分配内存块的函数

bool mem_pool_resize(size_t block_size)

{

size_t new_cnt = s_mem_pool.block_cnt + block_size;

struct student **tmp = (struct student **)realloc(s_mem_pool.mem,new_size*sizeof(struct student *));

if ( !tmp )

return false;

memset(tmp+s_mem_pool.block_cnt,0,size*sizeof(struct student*));

for ( size_t i = s_mem_pool.block_cnt;i < new_cnt;++i )

{

tmp[i] = (struct student *)calloc(BLOCK_SIZE,sizeof(struct student));

if ( !tmp[i] )

return false;

size_t j = 0;

for(;j < BLOCK_SIZE - 1;++j )

{

tmp[i][j].index = i*BLOCK_SIZE+j+1;

}

if ( i != new_cnt-1 )

tmp[i][j].index = (i+1)*BLOCK_SIZE;

else

tmp[i][j].index = -1;

}

s_mem_pool.free = s_mem_pool.alloc_cnt*BLOCK_SIZE;

s_mem_pool.mem = tmp;

s_mem_pool.block_cnt = new_cnt;

return true;

}

#define MEM_INC_SIZE 10

//初始化函数

bool mem_pool_init()

{

if ( !mem_pool_resize(MEM_INIT_SIZE) )

return false;

return true;

}

struct student *get_data()

{

if ( s_mem_pool.free == -1 )

{

if ( !mem_pool_resize(MEM_INC_SIZE) )

return NULL;

}

struct student *ret = s_mem_pool.mem[s_mem_pool.free/BLOCK_SIZE]+s_mem_pool.free%BLOCK_SIZE;

int pos = s_mem_pool.free;

s_mem_pool.free = ret->index;

ret->index = pos;

return ret;

}

void free_data(struct student *p)

{

if ( !p )

return;

int pos = p->index;

p->index = s_mem_pool.free;

s_mem_pool.free = pos;

}
这里不一样的地方主要在mem_pool_resize函数中,mem变成了2级指针,每次realloc的时候只需要分配指针数组的大小,无须拷贝对象,这样可以提高效率,但是为了在释放的时候把对象放回该放的位置,我们这里在结构体里加入了index变量,记录它的下标。在内存池里,它表示下个空闲对象的下标,在内存池外,它表示在内存池中的下标。总的来说满足了一个需求,却又带来了新的问题,有没有更好的方法呢,答案是肯定,不过今天先写到这里,明天继续。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: