innodb的mempool解析

innodb的mem/mem0pool.cc实现了一个高效的内存池,该模块采用的是经典的Buddy memory allocation算法(我觉得innodb并未对原算法进行什么改动)来实现内存块分配,回收,合并和拆解,这样做的一个显著优点是尽量避免了内存碎片,使得内存分配效率得到提升。

 

 

介绍一下它的大致构造,至于buddy算法的描述就去看wiki吧,这个内存池并未怎么改动这个算法

其实它主要由2个部分组成

一个就是一块很大的连续内存(buf),所有的内存块空间(area)都是来自这块内存,相同大小的空闲area会自己链起来

另外一个是一个area索引数组(free_list[64]),64个元素,分别指向的是不同空闲area链表

 

再介绍一些关键的代码

area结构体,结构体后面跟着的是真正分配给外面的内存

struct mem_area_struct{
    ulint        size_and_free; //最低的一位表示free状态, 其他位数表示size,都是2幂数,但是不能为20,不然会和free位冲突
UT_LIST_NODE_T(mem_area_t) free_list;       //指向下一个free的area,等大小的   };

 

mempool结构体

struct mem_pool_struct{
    byte*        buf;        //一大块连续的内存空间,area就放在上面
    ulint        size;                //buf的大小
    ulint        reserved;           //buf里已经用掉了多少内存
    mutex_t        mutex;    //用于并发锁这个结构体
    UT_LIST_BASE_NODE_T(mem_area_t)
            free_list[64];    //area索引数组,每个都指向一个空闲area链表,大小按2倍递增,最小的是MEM_AREA_MIN_SIZE大小,等于2个area结构体
};

 

 

创建一个mempool

mem_pool_create(
/*============*/
    ulint    size)    //buf的大小
{
    ...
    ...
    ...
  /*上面是省略一堆赋值的初始化过程,下面这个循环开始按照2幂大小格式化buf,每次循环基于最大化2幂划分原则,以size - used划出当前最大的2幂area,放入对应的pool->free_list[i],

   直到size - used < MEM_AREA_MIN_SIZE格式化结束,当然,MEM_AREA_MIN_SIZE肯定是大于20的*/
     used = 0;
while (size - used >= MEM_AREA_MIN_SIZE) {
        //算出一个刚好匹配的free_list[i] i
= ut_2_log(size - used); if (ut_2_exp(i) > size - used) { i--; }

        //由此可知buf中area的大小是从大到小排列 area
= (mem_area_t*)(pool->buf + used);
      mem_area_set_size(area, ut_2_exp(i)); mem_area_set_free(area, TRUE);
UNIV_MEM_FREE(MEM_AREA_EXTRA_SIZE
+ (byte*) area, ut_2_exp(i) - MEM_AREA_EXTRA_SIZE); UT_LIST_ADD_FIRST(free_list, pool->free_list[i], area); used = used + ut_2_exp(i); } ut_ad(size >= used); pool->reserved = 0; return(pool); }

 

 

mem_pool_fill_free_list这个函数将在mempool里free_list[i+1]中检查area链是否为NULL,如果area链不为空,free_list[i+1]拿出一个area2,分裂成2个area1,加入到free_list[i]中。也就是说如果当前free_list没有area了,则从更上一级拿area分裂,如果上一级也没有,则从再上一级去取,层层递归检查上去,找到一层area不为空的,然后层层递归分裂下来,分裂好的的area只会拿一个传下去分裂,另外一个还是保存在当前这一级的free_list里

ibool
mem_pool_fill_free_list(
/*====================*/
    ulint        i,    /*!< in: free list index */
    mem_pool_t*    pool)    /*!< in: memory pool */
{

...
...
...
area = UT_LIST_GET_FIRST(pool->free_list[i + 1]);
    //如果没有了,则递归到下一个free_list
if (area == NULL) { ...
   ...
   ...
ret
= mem_pool_fill_free_list(i + 1, pool); if (ret == FALSE) { return(FALSE); } //拿出一个area进行分裂 area = UT_LIST_GET_FIRST(pool->free_list[i + 1]); } ...
...
...
//到这步已经得到了适合分裂的area了,首先先把这个area从原来的list里删掉
UT_LIST_REMOVE(free_list, pool->free_list[i + 1], area);      //下面的操作是把原area分裂成area2和新的area,大小均为原来的一半,加入到对应的list中
     area2
= (mem_area_t*)(((byte*) area) + ut_2_exp(i)); UNIV_MEM_ALLOC(area2, MEM_AREA_EXTRA_SIZE); mem_area_set_size(area2, ut_2_exp(i)); mem_area_set_free(area2, TRUE); UT_LIST_ADD_FIRST(free_list, pool->free_list[i], area2); mem_area_set_size(area, ut_2_exp(i)); UT_LIST_ADD_FIRST(free_list, pool->free_list[i], area); return(TRUE); }

 

mem_area_alloc函数是为外界提供一块内存,如果没有对应大小的area会调用mem_pool_fill_free_list函数对大块的area进行拆解,如果确实没有适合的或者内存过大,则直接malloc分配

void*
mem_area_alloc(
    ulint*        psize, //传进去的是需求的大小,出来的是实际给的大小    
    mem_pool_t*    pool)    
{
    ...
    ...
    ...

   //规则申请的大小,算出一个刚好匹配的area size
= *psize; n = ut_2_log(ut_max(size + MEM_AREA_EXTRA_SIZE, MEM_AREA_MIN_SIZE));
mutex_enter(
&(pool->mutex)); mem_n_threads_inside++; ut_a(mem_n_threads_inside == 1);
    //在当前area链里获取area area
= UT_LIST_GET_FIRST(pool->free_list[n]); if (area == NULL) {

        //如果当前area是null的,则mem_pool_fill_free_list分裂大的area来补充 ret
= mem_pool_fill_free_list(n, pool);
        //实在没有malloc一个
if (ret == FALSE) { return(ut_malloc(size)); }
        //整合好再取一次area area
= UT_LIST_GET_FIRST(pool->free_list[n]); }

    //省略一堆检测合法性的代码     ...
  ...
  ...

   //设置该area为已用 mem_area_set_free(area, FALSE);     //从free_list中删掉 UT_LIST_REMOVE(free_list, pool->free_list[n], area);
    //重新计算已用的空间 pool
->reserved += mem_area_get_size(area); mem_n_threads_inside--; mutex_exit(&(pool->mutex)); ut_ad(mem_pool_validate(pool));
    //计算实际分配的大小
*psize = ut_2_exp(n) - MEM_AREA_EXTRA_SIZE; UNIV_MEM_ALLOC(MEM_AREA_EXTRA_SIZE + (byte*) area, *psize); return((void*)(MEM_AREA_EXTRA_SIZE + ((byte*) area))); }

 

 mem_area_get_buddy是通过给定的area找到和它配对的那个area(也就是同源分裂出来的那个area),参数area和size不能传错了,不然这个pool就全乱了,这和free()地址不能传错一样。mem_area_get_buddy主要是在mempool进行内存area回收的时候调用,因为如果同源area也是free状态,则合并操作,这个后面mem_area_free再说。

mem_area_t*
mem_area_get_buddy(
/*===============*/
    mem_area_t*    area,    /*!< in: memory area */
    ulint        size,    /*!< in: memory area size */
    mem_pool_t*    pool)    /*!< in: memory pool */
{
    mem_area_t*    buddy;

    ut_ad(size != 0);
    //这个判断很经典,其实他就是在计算pool->buf到area之间的内存按照size*2来划分,因为每次分裂area均成对出现,且都是按照半数平分, 所以只要看area位置是奇数还是偶数,如果是奇数,那么说明area前面的内存是已经完整的配对,
    //它的buddy在后面,若是偶数则反之。
if (((((byte*) area) - pool->buf) % (2 * size)) == 0) { /* The buddy is in a higher address */ buddy = (mem_area_t*)(((byte*) area) + size);
        //因为mempool_create格式化的时候,area是从大到小分配, 所以可能格式化出来的时候高位buddy不存在,为了避免对最原始的area判断时出错所以这里还要判断一下地址区间
        //但是像这种从大到小分配情况,低位buddy肯定存在,因为如果判断走低位buddy这条线,那么可以肯定指定的area是用过大area都是分裂出来的
if ((((byte*) buddy) - pool->buf) + size > pool->size) { /* The buddy is not wholly contained in the pool: there is no buddy */ buddy = NULL; } } else { /* The buddy is in a lower address; NOTE that area cannot be at the pool lower end, because then we would end up to the upper branch in this if-clause: the remainder would be 0 */ buddy = (mem_area_t*)(((byte*) area) - size); } return(buddy); }

 

 

回收一个area,和当前area的同源area也是空闲的,则合并成一个更大的, 这也是个递归调用,会层层合并上去

 

void
mem_area_free(
/*==========*/
    void*        ptr,    /*!< in, own: pointer to allocated memory
                buffer */
    mem_pool_t*    pool)    /*!< in: memory pool */
{
    mem_area_t*    area;
    mem_area_t*    buddy;
    void*        new_ptr;
    ulint        size;
    ulint        n;

    if (UNIV_LIKELY(srv_use_sys_malloc)) {
        free(ptr);

        return;
    }

   //判断传进来的地址是不是属于这个mempool的,
    if ((byte*) ptr < pool->buf || (byte*) ptr >= pool->buf + pool->size) {
        ut_free(ptr);

        return;
    }

    area = (mem_area_t*) (((byte*) ptr) - MEM_AREA_EXTRA_SIZE);
    //一些area的自检操作和debug信息
    ...
    ...
    ...

    //得到这个area的同源area
buddy = mem_area_get_buddy(area, size, pool); n = ut_2_log(size); mem_pool_mutex_enter(pool); mem_n_threads_inside++; ut_a(mem_n_threads_inside == 1);
     //判断buddy是否也是空闲的
if (buddy && mem_area_get_free(buddy) && (size == mem_area_get_size(buddy))) { /* The buddy is in a free list */       //合并2个area,考虑area的前后位置问题 if ((byte*) buddy < (byte*) area) { new_ptr = ((byte*) buddy) + MEM_AREA_EXTRA_SIZE; mem_area_set_size(buddy, 2 * size); mem_area_set_free(buddy, FALSE); } else { new_ptr = ptr; mem_area_set_size(area, 2 * size); } /* Remove the buddy from its free list and merge it to area */         //删掉原来buddy在空闲链表的位置 UT_LIST_REMOVE(free_list, pool->free_list[n], buddy); pool->reserved += ut_2_exp(n); mem_n_threads_inside--; mem_pool_mutex_exit(pool);
        //合并好了用新的area重复递归上面的过程 mem_area_free(new_ptr, pool);
return; } else { UT_LIST_ADD_FIRST(free_list, pool->free_list[n], area); mem_area_set_free(area, TRUE); ut_ad(pool->reserved >= size); pool->reserved -= size; } mem_n_threads_inside--; mem_pool_mutex_exit(pool); ut_ad(mem_pool_validate(pool)); }

 

 

posted @ 2012-06-02 15:05  白帆mvp  阅读(451)  评论(0)    收藏  举报