您的位置:首页 > 数据库 > MySQL

mysql源码学习笔记:内存管理模块MEM_ROOT

2017-03-30 23:30 555 查看
MEM_ROOT为mysql的内存管理模块,用于统一申请和释放内存,减少在堆中的内存申请操作的次数,以提升性能。

基础结构
申请的内存空间使用的结构体
typedef struct st_used_mem
{

struct st_used_mem *next; /*连接指针,连接当前链表下所有的结构体*/

unsigned int left; /*当前结构体中剩余的空间*/
unsigned int size; /*结构体申请的size*/
}
USED_MEM;

MEM_ROOT结构体
typedef struct st_mem_root
{
USED_MEM *free;
/*可以使用的预申请空间链表*/
USED_MEM *used;
/*使用中,且没有可用空间的预申请空间链表*/
USED_MEM *pre_alloc; /*初始时预申请的*/
size_t min_malloc /*剩余的最小空间,如果预申请的空间剩余大小小于min_alloc,将他移到used队列*/;
size_t block_size; /*每次申请内存块的基础大小*/
unsigned int block_num; /* 计算申请内存的参数,最终每次申请内存块大小为 block_size * (block_num >> 2) ,每次申请内存后该值+1*/
unsigned int first_block_usage; /*记录第一块内存块使用的次数,用作是否将第一块移入used链表的判断依据之一*/
size_t max_capacity; /*允许申请的最大空间地址的大小*/
size_t allocated_size; /*总计分配内存大小*/

void (*error_handler)(void); /*操作出错的是错误处理函数*/
}
MEM_ROOT;

一个使用中的MEM_ROOT结构如下:

其中每个内存块由一个USED_MEM结构体和内存空间组成,内存空间为向操作系统申请的内存。
MEM_ROOT结构体中free链表和used链表将所有的内存块连接起来(红色斜线卫为已经使用的内存)。

初始化
初始化过程主要为所有变量赋初始值。如果输入参数pre_alloc_size为0,那么初始化过程是不申请任何大小的内存空间的。
void init_alloc_root(...MEM_ROOT *mem_root, size_t block_size,size_t pre_alloc_size )

| mem_root->pre_alloc= 0;
| ...
/*每次申请空间的大小,用于后续申请空间使用*/
| mem_root->block_size= block_size - ALLOC_ROOT_MIN_BLOCK_SIZE;
/*如果指定了参数pre_alloc_size,那么申请一块大小为pre_alloc_size的内存空间*/
| if (pre_alloc_size)
| if ((mem_root->free= mem_root->pre_alloc = (USED_MEM*) my_malloc(,..pre_alloc_size+ ALIGN_SIZE(sizeof(USED_MEM)),

申请空间
mysql会首先使用my_malloc根据初始化时输入的空间大小预申请一块较大的空间,每次调用alloc_root函数,会在这块大的空间中,分配出一块空间地址作为alloc_root函数的返回(目的是将多次零散的malloc操作合并成一次大的malloc操作,以提升性能)。

当预申请的空间不足时。会重新申请一块大的地址空间。当alloc_root需要申请的空间大于预申请的空间时,mem_root会动态的调整预申请空间的大小来满足alloc_root的需求,如果max_capacity未设置,那么无论alloc_root申请多大的空间,mem_root均会申请足够大的预申请空间来满足它的需求(如果操作系统允许)。

void *alloc_root(MEM_ROOT *mem_root, size_t length)
/*
* 1.读取第一个内存空间节点,并判断是否应该将第一个空间节点放入used链表
* 2.在整个free链表中查看,查找第一个满足需求的(剩余空间超过length)的节点
*/
| (*(prev= &mem_root->free)) /*在free链表中读取第一个节点*/
| if ((*prev)->left < length) && /*该节点剩余空间不满足需求*/
mem_root->first_block_usage++ >= ALLOC_MAX_BLOCK_USAGE_BEFORE_DROP && /*该节点的使用次数达到了权值(10次)*/
(*prev)->left < ALLOC_MAX_BLOCK_TO_DROP /*该节点剩余空间小于4096*/
| next->next= mem_root->used; /*同时满足上述3个条件,将节点移入used链表*/
| mem_root->used= next;
| mem_root->first_block_usage= 0;
| for (next= *prev ; next && next->left < length ; next= next->next) /*遍历free链表,查找满足需求*/

| prev= &next->next;
/*
* 在free链表中没有找到满足需要的内存块节点,或者第一次使用mem_root,需要申请新的内存节点
*/
| if (! next) /*没找到满足条件的节点*/
| block_size= mem_root->block_size * (mem_root->block_num >> 2); /*mem_root 需要申请的大小*/
| get_size= length+ALIGN_SIZE(sizeof(USED_MEM)); /*需求的空间大小*/
| get_size= MY_MAX(get_size, block_size); /*两者取大最为申请值*/
| is_mem_available(mem_root, get_size) /*判断是否超出了允许的最大值*/
| if (mem_root->max_capacity
if ((mem_root->allocated_size + size) > mem_root->max_capacity)

| next = (USED_MEM*) my_malloc..., get_size,MYF(MY_WME | ME_FATALERROR) /*申请 get_size大小的空间*/
/*
* 更新mem_root参数,根据新的内存块的剩余空间的大小,将内存块放入free链表或者used链表
*/
| mem_root->allocated_size+= get_size;

| mem_root->block_num++;
| next->next= *prev; /*放入free链表*/
| ...
| if ((next->left-= (uint)length) < mem_root->min_malloc) /*如果剩余空间小于min_malloc,放入used链表 */

next->next= mem_root->used;

重置
复用MEM_ROOT,重置后,所有的内存块均被重置为未使用状态,所有的内存块均在free链表中。与全新的MEM_ROOT相比,重置后的mem_root并不会释放已经申请好的内存块。
mark_blocks_free(MEM_ROOT* root)
|last= &root->free;
|for (next= root->free; next; next= *(last= &next->next)) /*遍历free 链表,将所有内存块的left置位最大*/
next->left= next->size - (uint)ALIGN_SIZE(sizeof(USED_MEM));

|*last= next=root->used; /*将used链表接在free链表最后*/
| for (; next; next= next->next) /*遍历原used链表,将所有内存块的left置位最大*/
next->left= next->size - (uint)ALIGN_SIZE(sizeof(USED_MEM));
| root->used= 0;
| root->first_block_usage= 0;

释放
根据输入的参数,或者将MEM_ROOT重置,或者将所有申请的内存块释放。
void free_root(MEM_ROOT *root, myf MyFlags)
|if (MyFlags & MY_MARK_BLOCKS_FREE) /*参数为MY_MARK_BLOCKS_FREE,重置MEM_ROOT*/
mark_blocks_free(root);
/*
* 遍历free链表和used链表,将除pre_alloc外的所有内存块释放,重置pre_alloc
*/
| for (next=root->used; next ;)
my_free(old);
| for (next=root->free; next ;)
my_free(old);
| if (root->pre_alloc)
root->free=root->pre_alloc;

总结
MEM_ROOT模块为mysql内部的内存管理模块,使用MEM_ROOT模块,每次申请一个较大的内存块,将这个内存块根据需求拆分为小内存块使用。
对于申请内存size较少,并且申请较为频繁的情况,MEM_ROOT模块能比较好的提升性能。
而对于申请内存size较大的情况。使用MEM_ROOT与直接使用malloc申请,性能几乎持平。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: