8036中參數temptable_max_mmap/temptable_max_ram/tmp_table_size的含義(Allocator分配器)

為了說明清楚這3個參數的作用,我們有必要先從temptable引擎的內存分配器說起,這樣能夠更自然的帶出這3個參數的確切含義,同時能夠理解其內存分配的基本方式。

一、綜述

在temptable引擎中包含了一個內存分配器Allocator,這個分配器主要目的是為了高效的管理內存,并且為temptable中的各個容器提供所需的內存,譬如臨時表的索引插入數據就需要分配內存,每個線程都會初始化一個內存分配器。
總的來看這個內存分配器主要是以Block為單位進行分配的,并且每次需要分配的內存以Chunk為單位存放在Block中,這就減少了內存分配的次數,并且Block的分配是以1M,2M,4M,最大512M 成指數上升的,也就意味著如果需要大量的內存,分配的方式也會改變,因此比較靈活。而我們常見的 temptable_max_mmap/temptable_max_ram/tmp_table_size 參數就是在這一層生效的。

二、Block和Chunk

容器在分配內存的時候都是以Chunk為單位的,但是實際物理內存的分配是以Block為單位的,也就是說內存實際上實在Block中分配,如果不夠才會到OS層面去獲取,而一個Block到底多大下面在分析。每個Block有自己的metadata(在Block header中)如下,主要包含4個元素:

  • 物理內存類型 8字節,類型為分配的來自MMAP還是RAM
  • 當前block大小 8字節
  • chunk數量 8字節
  • first_pristine_offset 8字節,當前Block使用的總量包含了元數據,用m_offset(block 起點offset)+first_pristine_offset(Block使用總量) 就可以得到Block中下一個可用的位置(也叫做slot,但和上面的含義不同)。

而每個Chunk也有自己的metadata,主要存放的是本次分配chunk前first_pristine_offset的位置。我們大概將一個Block的表示如下,假定這個Block已經分配了2個Chunk,

block(block header)

       block metadata  chunk1 offset          chunk2 offset       
  |----   ----  ---- ---- | chunk1 header---data- |chunk2 header---data-|--------------------------------------------------------------------
  |                       |                                             |\
  |                       |                                             |
                                                                 next_available_slot
Block offset     pristine_offset =                           offset + pristine_offset
                    metadata  4*8                                定位新的內存點
                    +chunk 1  size
                    +chunk 2  size
                   保存的是偏移量

     
chunk1 header(8字節) = metadata  4*8 大小
chunk2 header(8字節) = metadata  4*8+chunk 1  size

并且通過Chunk的起點地址很容易的就能反推到Block起點地址,只需要使用Chunk內存的起點位置減去其元數據中存儲的pristine_offset就能夠快速反推Block的起點位置了如下,

inline uint8_t *Chunk::block() const { return m_offset - offset(); }

三、關于物理內存分配的策略

只要需要向OS申請內存,總是一次申請一個Block,temptable的內存分配主要包含2種策略,在8036中用到的是

  • Exponential_policy:size策略,主要是按照指數的方式增長內存,避免過多的物理內存分配影響性能,比如前面說的每個Block 1M,2M,4M 最大512M就是這個size 策略進行判斷的。
  • Prefer_RAM_over_MMAP_policy_obeying_per_table_limit:source策略,首先會判斷參數tmp_table_size是否超過,超過則直接報Result::RECORD_FILE_FULL,然后根據參數的設置temptable_max_mmap/temptable_max_ram,先考慮使用ram分配,不夠在進行mmap分配。如果都滿了則報Result::RECORD_FILE_FULL, 報錯后轉為Innodb物理臨時表。

如果涉及到物理內存分配和釋放的時候總是是調用兩個函數如下

  • static temptable::allocate_from :從MMAP或者RAM中分配內存,并且返回實際的地址,返回的地址會存儲在Block的m_offset中。
  • static temptable::deallocate_from:釋放內存,根據Block的m_offset就可以釋放這一片內存。

這里面包含了實際的分配方法,可以自行參考,需要注意的是MMAP分配內存的時候可能會出現一些包含以開頭mysql_temptable的臨時文件如下,

inline void *Memory<Source::MMAP_FILE>::fetch(size_t bytes) {
  File f = create_temp_file(file_path, mysql_tmpdir, "mysql_temptable.", mode,
                            UNLINK_FILE, MYF(MY_WME));

下面我們來看看分配的過程。

四、從全局block_pool中獲取一個slot

這個block_pool中主要包含了各個線程的第一個temptable block所在的位置,本質是一個容器數組其中每條數據只包含一個block屬性,并且為全局共享,lock free的靜態變量如下

  • static Lock_free_shared_block_pool<SHARED_BLOCK_POOL_SIZE> shared_block_pool;

其中每一個元素位置叫做slot,當線程需要建立temptable 臨時表的時候都會通過類方法去獲取一個有效的slot,如下,

temptable::Handler::Handler
m_shared_block(shared_block_pool.try_acquire(thd_thread_id(ha_thd()))),
調用方法
*try_acquire(size_t thd_id)

在線程退出的時候釋放最后一個Block(也就是建立的第一個Block),但是其他Block會在臨時表使用中或者使用后釋放,如下

try_release(size_t thd_id)

獲取到雖然獲取了Block,但是并沒有實際的分配內存。

五、Allocator的初始化和內存分配

當某個線程建立臨時表的時候,就需要對Allocator進行初始化,分配器中包含3個重要的元素,

  • m_shared_block:這個就來自前面說的block_pool中獲取的某個slot對應的block,這代表的是某個線程第一個block
  • m_state:這里面包含了一個當前分配的block的一個計數器和當前指向的block(current_block)
  • m_table_resource_monitor:當前表的內存統計,這是為了實現參數tmp_table_size所做的。

Allocator實現了2個必須要實現的功能就是內存分配和釋放,分別叫做,當分配內存的時候需要調用,

  • temptable::Allocator::allocate:從Block中分配Chunk需要的內存,首先需要查看的m_shared_block是否為空,如果為空則需要分配第一個Block,這個肯定是從OS內存中獲取一個1M的空間,如果不是第一分配,可能在第一個Block中存在剩余的空間則不需要再次從OS中獲取內存直接分配即可,如果第一個Block分配完了就需要新分配一個Block,并且從OS中獲取2M的空間了,并且由current_block指向,接下來可能Chunk需要的內存就在current_block分配了,如果current_block也滿了就再分配4M的Block,并且由current_block指向,依次類推。
  • temptable::Allocator::deallocate:從Block中刪除Chunk的內存,這部分基本和上面是相反的,先從Block中刪除這個Chunk,然后判斷Block Chunk的數量,如果為0了則這個Block整體從OS中釋放。但是需要注意的是第一個Block的內存不會釋放會持續到線程退出如下,
if (m_shared_block && (block == *m_shared_block)) { //如果m_shared_block存在 則不做任何事情,保留最后一個block
      // Do nothing. Keep the last block alive.
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容