MySQL MEM_ROOT詳細講解

這篇文章會詳細解說mysql中使用非常廣泛的mem_root的結構體,同時省去debug部分的信息,僅分析正常情況下,mysql中使用mem_root來做內存分配的部分。

在具體分析之前我們先例舉在該結構體使用過程中用到的一些宏:

#define?MALLOC_OVERHEAD?8?//分配過程中,需要保留一部分額外的空間  #define?ALLOC_MAX_BLOCK_TO_DROP?4096?//后續會繼續分析該宏的用途  #define?ALLOC_MAX_BLOCK_USAGE_BEFORE_DROP?10?//后續會繼續分析該宏的用途    #define?ALIGN_SIZE(A)?MY_ALIGN((A),sizeof(double))  #define?MY_ALIGN(A,L)?(((A)?+?(L)?-?1)?&amp;?~((L)?-?1))    #define?ALLOC_ROOT_MIN_BLOCK_SIZE?(MALLOC_OVERHEAD?+?sizeof(USED_MEM)?+?8)  /*?Define?some?useful?general?macros?(should?be?done?after?all?headers).?*/  /*作者:www.manongjc.com??*/  #define?MY_MAX(a,?b)?((a)?&gt;?(b)???(a)?:?(b))?//求兩個數值之間的最大值  #define?MY_MIN(a,?b)?((a)?<p>下面再來看看MEM_ROOT結構體相關的信息:</p><pre class="brush:php;toolbar:false">typedef?struct?st_mem_root  {  ????USED_MEM????*free;??????????????????/*?free?block?link?list的鏈表頭指針?*/  ????USED_MEM????*used;??????????????????/*?used?block?link?list的鏈表頭指針?*/  ????USED_MEM????*pre_alloc;?????????????/*?預先分配的block?*/  ????size_t????????min_malloc;?????????????/*?如果block剩下的可用空間小于該值,將會從free?list移動到used?list?*/  ????size_t????????block_size;?????????????/*?每次初始化的空間大小?*/  ????unsigned?int????block_num;??????????????/*?記錄實際的block數量,初始化為4?*/  ????unsigned?int????first_block_usage;??????/*?free?list中的第一個block?測試不滿足分配空間大小的次數?*/  ????void?(*error_handler)(?void?);??????????/*?分配失敗的錯誤處理函數?*/  }?MEM_ROOT;

以下是分配具體的block信息.

typedef?struct?st_used_mem  {?  ????struct?st_used_mem?*next;?//指向下一個分配的block  ????unsigned?int?left;?//該block剩余的空間大小  ????unsigned?int?size;?//該block的總大小  }?USED_MEM;

其實MEM_ROOT在分配過程中,是通過雙向鏈表來管理used和free的block:

MySQL MEM_ROOT詳細講解

MEM_ROOT的初始化過程如下:

void?init_alloc_root(?MEM_ROOT?*mem_root,?size_t?block_size,?size_t?pre_alloc_size?__attribute__(?(unused)?)?)  {  ????mem_root-&gt;free????????????=?mem_root-&gt;used?=?mem_root-&gt;pre_alloc?=?0;  ????mem_root-&gt;min_malloc????????=?32;  ????mem_root-&gt;block_size????????=?block_size?-?ALLOC_ROOT_MIN_BLOCK_SIZE;  ????mem_root-&gt;error_handler????????=?0;  ????mem_root-&gt;block_num????????=?4;?/*?We?shift?this?with?&gt;&gt;2?*/  ????mem_root-&gt;first_block_usage????=?0;  }

初始化過程中,block_size空間為block_size-ALLOC_ROOT_MIN_BLOCK_SIZE。因為在內存不夠,需要擴容時,是通過mem_root->block_num >>2 *?block_size 來擴容的,所以mem_root->block_num >>2 至少為1,因此在初始化的過程中mem_root->block_num=4(注:4>>2=1)。

MySQL MEM_ROOT詳細講解

下面來看看具體分配內存的步驟:

void?*alloc_root(?MEM_ROOT?*mem_root,?size_t?length?)  {  ????size_t????????get_size,?block_size;  ????uchar????????*?point;  ????reg1?USED_MEM????*next?=?0;  ????reg2?USED_MEM????**prev;    ????length?=?ALIGN_SIZE(?length?);  ????if?(?(*(prev?=?&amp;mem_root-&gt;free)?)?!=?NULL?)  ????{  ????????if?(?(*prev)-&gt;left?first_block_usage++?&gt;=?ALLOC_MAX_BLOCK_USAGE_BEFORE_DROP?&amp;&amp;  ?????????????(*prev)-&gt;left?next;?/*?Remove?block?from?list?*/  ????????????next-&gt;next????????????=?mem_root-&gt;used;  ????????????mem_root-&gt;used????????????=?next;  ????????????mem_root-&gt;first_block_usage????=?0;  ????????}  ????????for?(?next?=?*prev;?next?&amp;&amp;?next-&gt;left?next?)  ????????????prev?=?&amp;next-&gt;next;  ????}  ????if?(?!next?)  ????{???????/*?Time?to?alloc?new?block?*/  ????????block_size????=?mem_root-&gt;block_size?*?(mem_root-&gt;block_num?&gt;&gt;?2);  ????????get_size????=?length?+?ALIGN_SIZE(?sizeof(USED_MEM)?);  ????????get_size????=?MY_MAX(?get_size,?block_size?);    ????????if?(?!(next?=?(USED_MEM?*)?my_malloc(?get_size,?MYF(?MY_WME?|?ME_FATALERROR?)?)?)?)  ????????{  ????????????if?(?mem_root-&gt;error_handler?)  ????????????????(*mem_root-&gt;error_handler)();  ????????????DBUG_RETURN(?(void?*)?0?);??????????????????????????????/*?purecov:?inspected?*/  ????????}  ????????mem_root-&gt;block_num++;  ????????next-&gt;next????=?*prev;  ????????next-&gt;size????=?get_size;  ????????next-&gt;left????=?get_size?-?ALIGN_SIZE(?sizeof(USED_MEM)?);????/*?bug:如果該block是通過mem_root-&gt;block_size?*?(mem_root-&gt;block_num?&gt;&gt;?2)計算出來的,則已經去掉了ALIGN_SIZE(sizeof(USED_MEM),這里重復了。?*/  ????????*prev????????=?next;  ????}    ????point?=?(uchar?*)?(?(char?*)?next?+?(next-&gt;size?-?next-&gt;left)?);  /*TODO:?next?part?may?be?unneded?due?to?mem_root-&gt;first_block_usage?counter*/  /*?作者:www.manongjc.com?*/  ????if?(?(next-&gt;left?-=?length)?min_malloc?)  ????{???????????????????????????????????????????????????????????????????????/*?Full?block?*/  ????????*prev????????????????=?next-&gt;next;???????????????????/*?Remove?block?from?list?*/  ????????next-&gt;next????????????=?mem_root-&gt;used;  ????????mem_root-&gt;used????????????=?next;  ????????mem_root-&gt;first_block_usage????=?0;  ????}  }

上述代碼的具體邏輯如下:

1.查看free鏈表,尋找滿足空間的block。如果找到了合適的block,則:
1.1 直接返回該block從size-left處的初始地址即可。當然,在free list遍歷的過程中,會去判斷free?list
中第一個block中left的空間不滿足需要分配的空間,且該block中已經查找過了10次
(ALLOC_MAX_BLOCK_USAGE_BEFORE_DROP)都不滿足分配長度,且該block剩余空間小于
4k(ALLOC_MAX_BLOCK_TO_DROP),則將該block 移動到used鏈表中。

2.如果free鏈表中,沒有合適的block,則:
2.1 分配 mem_root->block_size * (mem_root->block_num >> 2)和length+ALIGN_SIZE(sizeof(USED_MEM))
中比較大的作為新的block內存空間。
2.2?根據該block的使用情況,將該block掛在used或者free鏈表上。

這里需要注意的是二級指針的使用:

for?(next=?*prev?;?next?&amp;&amp;?next-&gt;left?next)  prev=?&amp;next-&gt;next;  }

prev指向的是最后一個block的next指向的地址的地址:

MySQL MEM_ROOT詳細講解

所以將prev的地址替換為new?block的地址,即將該new block加到了free list的結尾:*prev=next;

MySQL MEM_ROOT詳細講解

總結:

MEM_ROOT的內存分配采用的是啟發式分配算法,隨著后續block的數量越多,單個block的內存也會越大:block_size= mem_root->block_size * (mem_root->block_num >> 2)?.

? 版權聲明
THE END
喜歡就支持一下吧
點贊13 分享