mimalloc是微軟最近開源的一個malloc實現,其實驗數據表明相比於jemalloc、tcmalloc等實現大約快了10%。其通過將空閑塊列表(Free List)進行分片(Sharding)來保證分配的內存有更好的空間的局部性,從而提升性能。在mimalloc中一共進行了4次Free List的Sharding。接下來我們會分別介紹這4個Free List的Sharding的位置以及其為什么要進行Free List的Sharding。
在Mimalloc頁中進行的Free List的Sharding
在其他的內存分配器的實現中,一般會為每一類大小的對象保留一個Free List,隨着內存的不斷分配與釋放,這個列表中的對象可能散布在整個地址空間中,因此內存分配的局部性較差。而在mimalloc中,其通過將內存分頁,每個頁負責一個特定大小的內存的分配,每個頁有一個Free List,因此內存分配的空間局部性較好。

其他內存分配器的Free List

mimalloc的Free List
Local Free List
mimalloc希望能夠限制內存分配與內存釋放在最差情況下的時間消耗,如果上層應用需要釋放一個非常大的結構體,其可能需要遞歸的釋放一些子結構體,因而可能帶來非常大的時間消耗。因而在koka與lean語言中,其運行時系統使用引用計數來追蹤各種數據,然后保留一個延遲釋放的隊列(deferred decrement list),當每次釋放的內存塊超過一定數量后就將剩余需要釋放的內存塊加入該隊列,在之后需要釋放的時候再進行釋放。那么下一個需要確定的問題是什么時候再去從該隊列中釋放內存。從延遲釋放隊列中繼續進行釋放的時機最好應當是內存分配器需要更多空間的時候,因此這需要內存分配器與上層應用的協作。
在mimalloc中,其提供一個回調函數,當進行了一定次數內存的分配與釋放后會主動調用該回調函數來通知上層應用。mimalloc在實現時檢測當前進行內存分配的頁的Free List是否為空,如果為空則調用該回調,但是為了避免用於一直不斷的分配與釋放內存,導致Free List一直不為空,而導致回調函數一直得不到回調。因此mimalloc將Free List第二次進行Sharding,將其分為Free List與Local Free List。
當內存在進行分配時會從對應頁的Free List中取得內存塊,而釋放時會將內存塊加入Local Free List中,因而在進行一定次數的內存分配后,Free List必定為空,此時可以進行deferred free的回調函數的調用。
Thread Free List
在mimalloc中每個堆都是一個Thread Local的變量,而每次進行內存分配時,其均會從這個Thread Local的堆中進行內存的分配,而釋放時即可能從該線程中釋放也可能從其他線程中進行釋放。如果進行內存釋放的線程是該堆的擁有者,則其釋放的內存會加入到對應頁的Local Free List中,而由於還可能有其他的線程來釋放這些內存,因此mimalloc第三次進行Free List的Sharding,將Local Free List分為Local Free List與Thread Free List。在進行內存的釋放時,如果釋放的線程為內存塊對應堆的擁有着則將其加入Local Free List,否則利用CAS操作將其加入Thread Free List中。mimalloc通過這次分割來保證堆的所有者線程在自己的堆上進行內存的釋放是無鎖的,從而提升一些性能上的表現。
Full List
第四次的Free List的Sharding其實來自於mimalloc自身的實現,其內存分配的偽代碼如下。由於在mimalloc中每個堆中都有一個數組pages,該數組中每個元素都是一個由相應大小的頁組成的隊列;同時還有一個pages_direct的數組,該數組中每個元素對應一個內存塊的大小類型,每個元素均為指向負責對應大小內存塊分配的頁的指針。因此mimalloc在進行內存分配時會首先從該數組指向的頁中嘗試進行分配,如果分配失敗則調用malloc_generic,在該函數中會遍歷pages數組中對應大小的隊列,此時如果對應的隊列中有很多頁均是滿的,且隊列很長那么每次分配的時候都會進行隊列的遍歷,導致性能的損失。
void* malloc_small( size_t n ) { heap_t* heap = tlb; page_t* page = heap->pages_direct[(n+7)>>3]; block_t* block = page->free; if (block==NULL) return malloc_generic(heap,n); page->free = block->next; page->used++; return block; }
因此mimalloc構建了一個Full List,將所有已經沒有空閑空間的頁放入該隊列中,僅當該頁中有一些空閑空間被釋放后才會將其放回pages對應的隊列中。而在由於內存的釋放可能由對應堆的擁有者線程進行也可能由其他線程進行,因此需要一定的方式提醒對應的堆該頁已經有空閑塊了,同時為了避免使用鎖導致的開銷,mimalloc通過加入一個Thread Delayed Free List,如果一個頁處於Full List中,那么在釋放時會將內存塊加入Thread Delayed Free List中,該隊列會在調用malloc_generic時進行檢測與清除(由於時Thread Local的堆,因此僅可能是擁有者來進行),因此此時僅需通過原子操作即可完成。那么還有一個問題是當釋放內存的時候,其他線程如何知道是將內存塊加入Thread Free List中還是Thread Delayed Free List中。mimalloc通過設置NORMAL、DELAYED、DELAYING三種狀態來完成該操作。
總結
mimalloc通過將Free List進行分割,保證分配的內存具有較好的局部性並避免了鎖的使用,從而獲得了更好的性能。文章如果有哪里有問題,歡迎提出,對該項目感興趣的可以去看一下其倉庫
1,或者參考這篇文章
2。