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)); }
浙公网安备 33010602011771号