mimalloc是微軟去年6月開源的(竟然拖了半年才開始寫筆記)新的內存分配器, 其最早由Daan Leijen開發, 為Koka與Lean等語言的runtime system提供內存管理.
 根據官方文檔描述, mimalloc在各類benchmark上性能均優於其它主流allocator(分別超過tcmalloc與jemalloc 7%與14%)且消耗更少內存.
 mimalloc當前版本為1.2.2, 在這里可以獲取源碼與文檔.
1. 編譯與使用
mimalloc使用cmake作為構建工具, 執行以下命令即可編譯.
 成功編譯后在構建目錄下會生成所需(libmimalloc)的動態庫/靜態庫以及測試工具.
mkdir -p [dir to build]
cd [dir to build]
cmake [source path]
make
 
        可以通過在源文件中包含mimalloc.h並增加-lmimalloc選項來使用mimalloc.
 mimalloc可以與其它allocator共存, 對於cmake構建可以使用以下命令設置:
 target_link_libraries([program] PUBLIC mimalloc)
 如果不希望重新編譯也可以通過設置環境變量來替換:
 env LD_PRELOAD=[path/to/libmimalloc.so] [program]
如果需要打印libmalloc的調試信息, 可以設置環境變量(注意后者需要debug版本):
 env MIMALLOC_VERBOSE=1 [program]
 env MIMALLOC_SHOW_STATS=1 [program]
其它選項:
 打印錯誤/告警信息: MIMALLOC_SHOW_ERRORS=1
 使用huge page特性: MIMALLOC_LARGE_OS_PAGES=1
嘗試編譯一個測試用例:
[23:18:20] hansy@hansy:~$ cat 1.c
#include <sys/time.h>
int main() {
  struct timeval last;
  struct timeval next;
  gettimeofday(&last, 0);
  for (int i = 0; i < 10000000; ++i) {
    int *p = malloc(i * sizeof(int));
    free(p);
  }
  gettimeofday(&next, 0);
  printf("%llu.%06llu\n",
    (next.tv_usec > last.tv_usec ? next.tv_sec - last.tv_sec : next.tv_sec - 1 - last.tv_sec),
    (next.tv_usec > last.tv_usec ? next.tv_usec - last.tv_usec : 1000000 + next.tv_usec - last.tv_usec));
  return 0;
}
[23:18:25] hansy@hansy:~$ gcc 1.c -w && ./a.out 
8.606776
[23:18:42] hansy@hansy:~$ env LD_PRELOAD=./mimalloc/build_release/libmimalloc.so ./a.out 
1.314598
[23:18:57] hansy@hansy:~$ env MIMALLOC_VERBOSE=1 LD_PRELOAD=./mimalloc/build_release/libmimalloc.so ./a.out 
mimalloc: process init: 0x7f4b0d558740
mimalloc: option 'large_os_pages': 0
mimalloc: option 'secure': 0
mimalloc: option 'page_reset': 0
mimalloc: option 'cache_reset': 0
1.323777
mimalloc: option 'show_stats': 0
heap stats:     peak      total      freed       unit      count  
   elapsed:     1.324 s
   process: user: 1.299 s, system: 0.012 s, faults: 0, reclaims: 479, rss: 2.6 mb
mimalloc: process done: 0x7f4b0d558740
 
         
         
        2. 設計思想
建議首先閱讀technical report, 配合代碼可以有更好的理解. 這里是原文鏈接.
 以下是論文摘要:
 mimalloc的設計背景
 現代allocator需要平衡各類需求, 包括性能, 安全, 並行化以及其它應用程序需求的特性. 在開發一個用於Koka與Lean的runtime system時微軟遇到兩類情況:
 一是存在許多short lived的小對象分配, 一個定制的allocator表現能優於jemalloc等主流通用allocator.
 二是這些runtime system都使用RC機制來管理內存對象. 在回收較大的數據結構時為減少pause需要推遲減少引用計數. 為獲取最佳性能我們需要allocator的協助: 在面臨內存壓力時回復之前推遲的decrement.
為解決以上問題, 微軟引出了free list sharding(分片空閑鏈表)設計.
 傳統allocator往往根據內存塊大小分類(size-class)管理free list, 一類大小對應一個鏈表. 其好處是分配給定大小內存可以達到O(1), 缺點是程序局部性較差, 同一數據結構的內存塊散布在整個堆上.
 為改善局部性mimalloc修改了鏈表設計, 先將堆被分為一系列(用於分配不同大小內存塊)的頁(mimalloc page, 當前設定64K), 每頁通過一個free list管理. 那么每類大小的內存分配接口可以實現如下:
struct block_t { struct block_t *next; }
void *malloc_by_size(page_t *page, int size) {
  if (block_t *block = page->free) {
    page->free = block->next;
    page->used++;
    return block;
  }
  return malloc_generic(size); // slow path
}
 
        對於swift與python等使用RC機制的語言釋放較大的數據結構會導致遞歸調用free, 影響程序性能. 對此通常可以限制free調用次數並將剩余指針加入deferred decrement list. 問題在於何時重新開始釋放? 就像kernel一樣, 只有面臨內存壓力時再釋放是最優解, 因此這需要allocator配合. 在malloc_generic(slow path)中mimalloc會調用用戶定義的deferred_free回調.
 然而假如用戶在一頁內來回分配釋放內存導致slow path永遠不被調用怎么辦? 因此mimalloc再次為free list分片: 每頁增加一個local free list. 當釋放對象時將其放入local free list, 保證free list最終會變空. 在分配接口中再將local free list賦值給free list回收使用.
 在mimalloc中page歸屬於線程堆, 線程僅從本地堆中分配內存, 但其它線程同樣可以釋放本線程的堆. 為避免引入鎖, mimalloc再為每頁增加一個thread free list用於記錄由其它線程釋放(本線程申請)的內存, 當非本地釋放發生時使用atomic_push(&page->thread_free, p)將其放入thread free list中.
void atomic_push(block_t **list, block_t *block) {
  do {
    block->next = *list;
  } while (!atomic_compare_and_swap(list, block, block->next));
}
 
         
         
        thread free list的設計不光減少了線程對本頁的競爭, 同樣減少了線程對不同頁的競爭. 類似於local free list, thread free list的回收也在分配接口中.
 這樣設計的優點:
 分期釋放大塊數據結構, 減少停頓
 維持確定的心跳, 保證RC
 提升thread free list的一致性
 lock free
 傳統allocator聚焦於減少內存使用, 加速分配/釋放或多線程擴展, mimalloc證明了提升程序內存局部性同樣可以提升allocator性能.
3. 源碼分析
mimalloc源碼非常小(僅3.5k loc), 建議閱讀源碼配合本文.
 先來看下內存組織結構, 類似於ptmalloc中malloc_chunk -> bin -> malloc_state, mimalloc也是三級架構mi_block_t -> mi_page_t -> mi_heap_s.
typedef uintptr_t mi_encoded_t;
typedef struct mi_block_s {
  mi_encoded_t next;
} mi_block_t;
 
        mimalloc中內存塊的最小單位是mi_block_t, 區別於ptmalloc中malloc_chunk復雜的結構, mi_block_t只有一個指向(同樣大小的)下一空閑內存塊的指針.
 這是因為在mimalloc中所有內存塊都是size classed page中分配的, 不需要對空閑內存塊做migrate, 因此不用保存本塊大小, (物理連續的塊的)狀態及大小等信息.
typedef union mi_page_flags_u {
  uint16_t value;
  struct {
    bool has_aligned;
    bool in_full;
  };
} mi_page_flags_t;
typedef struct mi_page_s {
  // 該頁在段(segment)中的索引, page = &segment->pages[page->segment_idx]
  // 在段分配時初始化(mi_segment_alloc), 用於計算該頁指向的實際內存地址(segment + idx * mi_segment_t->page_shift)
  uint8_t segment_idx;
  // 標記該頁是否已使用(已分配內存)
  bool segment_in_use:1;
  // 復位標記(什么時候復位?)
  bool is_reset:1;
  // 頁屬性標記
  mi_page_flags_t flags;
  // 該頁當前內存塊的容量
  // 這里有幾個概念:
  // 頁的大小 - 根據頁的類型(SMALL / MEDIUM / LARGE)決定
  // 頁的最大分配內存塊數量 - 即reserved, 根據(頁大小 / 該頁管理的塊大小)決定
  // 頁的當前管理內存塊數量 - 即capacity, 由於管理所有塊需要賦值mi_block_t導致RSS膨脹, 因此空閑塊的鏈表不是一次性初始化的
  // 頁的當前分配內存塊數量 - 即used
  // 頁的實際活動內存塊數量 - used - thread_free - local_free
  uint16_t capacity;
  // 保留未申請的內存所能分配的內存塊的個數, 在mi_page_init中初始化, 為page_size / mi_page_t->block_size
  uint16_t reserved;
  // free鏈表, 指向當前空閑內存塊, malloc總是從該鏈表獲取空閑內存塊
  mi_block_t *free;
  // 隨機cookie, 用於安全特性
  uintptr_t cookie;
  // 引用計數, 注意該計數同樣將locl_free與thread_free包含在內
  size_t used;
  // 申請內存的線程釋放內存時掛入該鏈表, 只有在free耗盡時才將其賦值給free
  mi_block_t *local_free;
  volatile uintptr_t thread_freed;
  // 類似於local_free, 但是只有其它線程釋放內存時掛入該鏈表, 使用cas解決競爭
  volatile mi_thread_free_t thread_free;
  // 該頁管理的內存塊的大小, 一個頁只管理一種大小的內存塊
  size_t block_size;
  // 頁所屬哪個堆
  mi_heap_t *heap;
  // 雙向鏈表記錄(歸屬於本堆的)管理同一大小內存塊的所有頁
  struct mi_page_s *next;
  struct mi_page_s *prev;
} mi_page_t;
 
        mimalloc的二級結構mi_page_t前文已經描述很多了, 其它的成員見注釋.
typedef struct mi_tld_s mi_tld_t;
typedef struct mi_page_queue_s {
  mi_page_t *first;
  mi_page_t *last;
  size_t block_size;
} mi_page_queue_t;
struct mi_heap_s {
  // thread local data
  mi_tld_t *tld;
  // LRU鏈表數組, 每個元素存放分配8-1024字節(MI_SMALL_WSIZE_MAX為128)大小內存塊的頁用於加速分配
  // 若元素為空說明分配對應大小內存塊的空閑頁不存在, 需要先回收內存或分配新頁
  // _mi_heap_get_free_small_page確保返回值不會大於MI_SMALL_WSIZE_MAX*sizeof(void*),為什么還要加2?
  mi_page_t *pages_free_direct[MI_SMALL_WSIZE_MAX + 2];
  // 頁隊列數組, 保存所有用於分配小於512K內存塊的頁, 最后一個元素保存已滿的頁
  // 數組中每個元素與保存的頁大小是非線性對應的:
  // 8-64字節每個大小各占一個元素(一共8個)
  // 之后每4個元素管理的大小區間翻倍(512K / 8 = 2**16, 16 << 2 = 64個元素)
  // 連續4個元素均分該大小區間
  mi_page_queue_t pages[MI_BIN_FULL + 1];
  volatile mi_block_t *thread_delayed_free;
  uintptr_t thread_id;
  uintptr_t cookie;
  uintptr_t random;
  size_t page_count;
  bool no_reclaim;
};
 
        三級結構mi_heap_s用於描述一個堆. 每個堆對應一個線程, 但線程可能不止一個堆.
 堆結構的初始化: 分為主堆與線程堆. 為防止在某些平台上訪問thread local data會分配內存導致死循環, 主堆是靜態分配的數據結構(盡管仍然會調用mi_process_init/mi_process_done做初始化/去初始化), 而線程堆通過mi_thread_init(被mi_malloc_generic調用)初始化.
typedef struct mi_segment_s {
  // 雙向鏈表用來管理mi_segment_t
  struct mi_segment_s *next;
  struct mi_segment_s *prev;
  struct mi_segment_s *abandoned_next;
  size_t abandoned;
  // 該段內已使用的頁計數
  size_t used;
  // 該段容納頁的最大數量, mi_segment_alloc中初始化, 對於HUGE頁固定為1, 其它為段大小(MI_SEGMENT_SIZE = 4M) / page_size
  size_t capacity;
  // 段大小, 對於HUGE頁大小為實際申請長度加管理字節及對齊, 對於其它頁為4M
  // 可以通過mi_segment_size獲取segment_size與segment_info_size
  size_t segment_size;
  // 管理頭部, 包含(mi_segment_t結構與capacity * mi_page_t結構)
  // 對於非HUGE頁, 管理字節在段空間的首部, 與第一個頁空間共用空間, 因此第一個頁實際可用地址需要額外計算
  // 如開啟安全選項, 管理頭會額外占用一個os頁
  size_t segment_info_size;
  uintptr_t cookie;
  // 記錄頁大小對應的偏移(MI_LARGE_PAGE_SHIFT / MI_MEDIUM_PAGE_SHIFT / MI_SMALL_PAGE_SHIFT)
  size_t page_shift;
  uintptr_t thread_id;
  // 頁類型, 與大小相關
  mi_page_kind_t page_kind;
  // 變長數組, 實際是capacity個元素的mi_page_t結構, 與mi_segment_t結構一起占據第一個頁
  mi_page_t pages[1];
} mi_segment_t;
 
        最后來看一個特殊的數據結構mi_segment_t. 為防止頻繁調用mmap導致系統vma碎片化, mimalloc采用批量申請內存的方式向系統申請內存. 申請的內存使用mi_segment_t結構來管理, mi_page_t通過mi_segment_t來分配. 一個段的大小是4M(on 64bit platform), 可以被分為64 * 64K(小頁), 8 * 512K(中頁)或1 * 4M(大頁)(再大的內存采用直接申請的方式), 相關大小的設置可以看MI_SMALL_PAGE_SIZE / MI_MEDIUM_PAGE_SIZE / MI_LARGE_PAGE_SIZE幾個宏.
20.01.18 補兩張圖
 
 
4. 接口實現
alloc-override*.c中定義如何將標准malloc/free及c++的new/delete操作符alias為mi_*接口, 比如malloc被alias為mi_malloc:
#define MI_FORWARD(fun) __attribute__((alias(#fun), used, visibility("default"), copy(fun)))
void *malloc(size_t size) mi_attr_noexcept MI_FORWARD1(mi_malloc, size);
 
        先來看下mi_malloc的實現, 根據申請內存大小選擇fast path或slow path.
void *mi_heap_malloc(mi_heap_t *heap, size_t size) {
  // MI_SMALL_SIZE_MAX = 128 * sizeof(void *)
  if (size <= MI_SMALL_SIZE_MAX) {
    return mi_heap_malloc_small(heap, size);
  }
  return _mi_malloc_generic(heap, size);
}
void *mi_malloc(size_t size) {
  return mi_heap_malloc(mi_get_default_heap(), size);
}
 
        對於小於1M的內存分配會選擇fast path. mi_heap_malloc_small會嘗試從當前堆的free鏈表中獲取空閑塊, 若不存在則回退slow path. _mi_wsize_from_size會將傳入的大小對齊到sizeof(void *)(machine word size)邊界后取index, pages_free_direct數組存放了包含對應大小的空閑塊的page.
void *mi_heap_malloc_small(mi_heap_t *heap, size_t size) {
  mi_page_t *page = _mi_heap_get_free_small_page(heap, size);
  return _mi_page_malloc(heap, page, size);
}
size_t _mi_wsize_from_size(size_t size) {
  return (size + sizeof(uintptr_t) - 1) / sizeof(uintptr_t);
}
mi_page_t *_mi_heap_get_free_small_page(mi_heap_t *heap, size_t size) {
  return heap->pages_free_direct[_mi_wsize_from_size(size)];
}
void *_mi_page_malloc(mi_heap_t *heap, mi_page_t *page, size_t size) {
  mi_block_t *block = page->free;
  if (block == NULL) {
    return _mi_malloc_generic(heap, size); // slow path
  }
  page->free = mi_block_next(page, block);
  page->used++;
  block->next = 0;
  return block;
}
 
        如果申請的內存大於1M或指定大小的空閑塊不存在則調用_mi_malloc_generic. 如前文所述, slow path首先會嘗試釋放之前未釋放的內存.
 用戶可以注冊deferred_free用於釋放之前未釋放的對象, 該接口會在_mi_deferred_free中被調用. 另外此時也會釋放其它線程延遲釋放的內存塊(需要該堆設置MI_USE_DELAYED_FREE標記).
 在釋放內存后再根據申請大小選擇從候補隊列或申請大頁來分配內存, 找到候補頁后調用fast path分配空閑塊(有可能再次分配失敗重新進入slow path嗎?).
void *_mi_malloc_generic(mi_heap_t *heap, size_t size) {
  if (!mi_heap_is_initialized(heap)) {
    mi_thread_init();
    heap = mi_get_default_heap();
  }
  _mi_deferred_free(heap, false);
  _mi_heap_delayed_free(heap);
  mi_page_t *page;
  // MI_LARGE_SIZE_MAX = 512K, 32bit platform減半
  if (size > MI_LARGE_SIZE_MAX) {
    if (size >= (SIZE_MAX - MI_MAX_ALIGN_SIZE)) {
      page = NULL;
    }
    else {
      page = mi_huge_page_alloc(heap, size);
    }
  }
  else {
    page = mi_find_free_page(heap, size);
  }
  if (page == NULL) return NULL;
  return _mi_page_malloc(heap, page, size);
}
static mi_deferred_free_fun *deferred_free = NULL;
void _mi_deferred_free(mi_heap_t *heap, bool force) {
  heap->tld->heartbeat++;
  if (deferred_free != NULL) {
    deferred_free(force, heap->tld->heartbeat);
  }
}
void _mi_heap_delayed_free(mi_heap_t *heap) {
  mi_block_t *block;
  do {
    block = (mi_block_t*)heap->thread_delayed_free;
  } while (block != NULL && !mi_atomic_compare_exchange_ptr((volatile void**)&heap->thread_delayed_free, NULL, block));
  while(block != NULL) {
    mi_block_t *next = mi_block_nextx(heap->cookie, block);
    if (!_mi_free_delayed_block(block)) {
      mi_block_t *dfree;
      do {
        dfree = (mi_block_t *)heap->thread_delayed_free;
        mi_block_set_nextx(heap->cookie, block, dfree);
      } while (!mi_atomic_compare_exchange_ptr((volatile void**)&heap->thread_delayed_free, block, dfree));
    }
    block = next;
  }
}
 
        來看下如何選擇一個合適的頁來申請內存. 對於分配小塊內存的頁由mi_heap_t->pages[]管理, 根據申請大小計算index獲取mi_page_queue_t, 其鏈表頭指向的頁是最近分配該大小(LRU)的頁. 該頁可能已滿, 所以先要調用_mi_page_free_collect對該頁做free操作.
 如果釋放操作后仍然沒有空閑塊表明LRU頁不存在指定大小的空閑塊, 需要調用mi_page_queue_find_free_ex遍歷所有頁來回收內存. 該接口會遍歷隊列中的每個頁並嘗試釋放內存, 此處有三種情況:
 一個頁經過回收后存在空閑塊則選擇該頁. 需要注意的是如果發現該頁完全被回收則會嘗試釋放整個頁(稱作retire), 第一個空閑頁又會保存到循環外retire防止之后的頁都非空導致無內存分配.
 如果發現一個頁雖然沒有空閑塊但是capacity小於reserved說明該頁還能擴展空閑塊. mi_page_extend_free負責擴展空閑塊, 查看該接口可以發現空閑塊的擴展並不是無限制的, 而是每次不超過一個物理頁(OS page), 這里分次extend的原因應該是防止RSS段無意義的增長(初始化free鏈表時會寫地址導致物理內存的分配).
 如果發現一個頁已滿則將其掛入full鏈表(mi_page_to_full), 防止多次遍歷降低效率. mi_heap_t->pages[]的最后一個index用來存放已滿的頁(如果該頁空閑以后, 什么時候將其取回到正常鏈表?).
 在遍歷所有頁后仍然未能獲取合適的頁那么調用mi_page_fresh獲取一個全新頁.
mi_page_t *mi_find_free_page(mi_heap_t *heap, size_t size) {
  mi_page_queue_t *pq = mi_page_queue(heap, size);
  mi_page_t *page = pq->first;
  if (page != NULL) {
    if (mi_option_get(mi_option_secure) >= 3 && page->capacity < page->reserved && ((_mi_heap_random(heap) & 1) == 1)) {
      mi_page_extend_free(heap, page, &heap->tld->stats);
    }
    else {
      _mi_page_free_collect(page);
    }
    if (mi_page_immediate_available(page)) {
      return page;
    }
  }
  return mi_page_queue_find_free_ex(heap, pq);
}
mi_page_queue_t *mi_page_queue(const mi_heap_t *heap, size_t size) {
  return &((mi_heap_t*)heap)->pages[_mi_bin(size)];
}
bool mi_page_immediate_available(const mi_page_t *page) {
  return (page->free != NULL);
}
void _mi_page_free_collect(mi_page_t *page) {
  if (page->local_free != NULL) {
    if (page->free == NULL) {
      page->free = page->local_free;
    }
    else {
      mi_block_t *tail = page->free;
      mi_block_t *next;
      while ((next = mi_block_next(page, tail)) != NULL) {
        tail = next;
      }
      mi_block_set_next(page, tail, page->local_free);
    }
    page->local_free = NULL;
  }
  if (mi_tf_block(page->thread_free) != NULL) {
    mi_page_thread_free_collect(page);
  }
}
mi_page_t *mi_page_queue_find_free_ex(mi_heap_t *heap, mi_page_queue_t *pq) {
  mi_page_t *rpage = NULL;
  size_t count = 0;
  size_t page_free_count = 0;
  mi_page_t *page = pq->first;
  while( page != NULL)
  {
    mi_page_t *next = page->next;
    count++;
    _mi_page_free_collect(page);
    if (mi_page_immediate_available(page)) {
      if (page_free_count < 8 && mi_page_all_free(page)) {
        page_free_count++;
        if (rpage != NULL) _mi_page_free(rpage, pq, false);
        rpage = page;
        page = next;
        continue;
      }
      break;
    }
    if (page->capacity < page->reserved) {
      mi_page_extend_free(heap, page, &heap->tld->stats);
      break;
    }
    mi_page_to_full(page, pq);
    page = next;
  }
  mi_stat_counter_increase(heap->tld->stats.searches, count);
  if (page == NULL) {
    page = rpage;
    rpage = NULL;
  }
  if (rpage != NULL) {
    _mi_page_free(rpage, pq, false);
  }
  if (page == NULL) {
    page = mi_page_fresh(heap, pq);
  }
  return page;
}
mi_page_extend_free(mi_heap_t *heap, mi_page_t *page, mi_stats_t *stats) {
  if (page->free != NULL) return;
  if (page->capacity >= page->reserved) return;
  size_t page_size;
  _mi_page_start(_mi_page_segment(page), page, &page_size);
  if (page->is_reset) {
    page->is_reset = false;
    mi_stat_decrease(stats->reset, page_size);
  }
  mi_stat_increase( stats->pages_extended, 1);
  size_t extend = page->reserved - page->capacity;
  size_t max_extend = MI_MAX_EXTEND_SIZE / page->block_size;
  if (max_extend < MI_MIN_EXTEND) max_extend = MI_MIN_EXTEND;
  if (extend > max_extend) {
    extend = (max_extend==0 ? 1 : max_extend);
  }
  mi_page_free_list_extend(heap, page, extend, stats);
}
void mi_page_to_full(mi_page_t *page, mi_page_queue_t *pq) {
  _mi_page_use_delayed_free(page, MI_USE_DELAYED_FREE);
  if (page->flags.in_full) return;
  mi_page_queue_enqueue_from(&page->heap->pages[MI_BIN_FULL], pq, page);
  mi_page_thread_free_collect(page);
}
mi_page_t *mi_page_fresh(mi_heap_t *heap, mi_page_queue_t *pq) {
  mi_page_t *page = pq->first;
  if (!heap->no_reclaim &&
      _mi_segment_try_reclaim_abandoned(heap, false, &heap->tld->segments) &&
      page != pq->first)
  {
    page = pq->first;
    if (page->free != NULL) return page;
  }
  page = mi_page_fresh_alloc(heap, pq, pq->block_size);
  if (page==NULL) return NULL;
  return page;
}
 
        再來看下大頁的獲取, mi_huge_page_alloc也是調用mi_page_fresh_alloc獲取新頁, 后者根據傳入的大小決定申請何種頁, 最終都是調用mi_segment_page_alloc.
mi_page_t *mi_huge_page_alloc(mi_heap_t *heap, size_t size) {
  size_t block_size = _mi_wsize_from_size(size)  *sizeof(uintptr_t);
  mi_page_queue_t *pq = mi_page_queue(heap,block_size);
  mi_page_t *page = mi_page_fresh_alloc(heap,pq,block_size);
  if (page != NULL) {
    mi_heap_stat_increase( heap, huge, block_size);
  }  
  return page;
}
mi_page_t *mi_page_fresh_alloc(mi_heap_t *heap, mi_page_queue_t *pq, size_t block_size) {
  mi_page_t *page = _mi_segment_page_alloc(block_size, &heap->tld->segments, &heap->tld->os);
  if (page == NULL) return NULL;
  mi_page_init(heap, page, block_size, &heap->tld->stats);
  mi_heap_stat_increase( heap, pages, 1);
  mi_page_queue_push(heap, pq, page);
  return page;
}
mi_page_t *_mi_segment_page_alloc(size_t block_size, mi_segments_tld_t *tld, mi_os_tld_t *os_tld) {
  mi_page_t *page;
  if (block_size <= (MI_SMALL_PAGE_SIZE / 16) * 3)
    page = mi_segment_small_page_alloc(tld,os_tld);
  else if (block_size <= (MI_MEDIUM_PAGE_SIZE / 16) * 3)
    page = mi_segment_medium_page_alloc(tld, os_tld);
  else if (block_size < (MI_LARGE_SIZE_MAX - sizeof(mi_segment_t)))
    page = mi_segment_large_page_alloc(tld, os_tld);
  else
    page = mi_segment_huge_page_alloc(block_size,tld,os_tld);
  return page;
}
mi_page_t *mi_segment_page_alloc(mi_page_kind_t kind, size_t page_shift, mi_segments_tld_t *tld, mi_os_tld_t *os_tld) {
  mi_segment_queue_t *free_queue = mi_segment_free_queue_of_kind(kind,tld);
  if (mi_segment_queue_is_empty(free_queue)) {
    mi_segment_t *segment = mi_segment_alloc(0,kind,page_shift,tld,os_tld);
    if (segment == NULL) return NULL;
    mi_segment_enqueue(free_queue, segment);
  }
  return mi_segment_page_alloc_in(free_queue->first,tld);
}
mi_segment_t *mi_segment_alloc(size_t required, mi_page_kind_t page_kind, size_t page_shift, mi_segments_tld_t *tld, mi_os_tld_t *os_tld) {
  size_t capacity;
  if (page_kind == MI_PAGE_HUGE) {
    capacity = 1;
  }
  else {
    size_t page_size = (size_t)1 << page_shift;
    capacity = MI_SEGMENT_SIZE / page_size;
  }
  size_t info_size;
  size_t pre_size;
  size_t segment_size = mi_segment_size(capacity, required, &pre_size, &info_size);
  size_t page_size = (page_kind == MI_PAGE_HUGE ? segment_size : (size_t)1 << page_shift);
  mi_segment_t *segment = NULL;
  segment = mi_segment_cache_find(tld,segment_size);
  if (segment != NULL && mi_option_is_enabled(mi_option_secure) && (segment->page_kind != page_kind || segment->segment_size != segment_size)) {
    _mi_os_unprotect(segment,segment->segment_size);
  }
  if (segment == NULL) {
    segment = (mi_segment_t*)_mi_os_alloc_aligned(segment_size, MI_SEGMENT_SIZE, true, os_tld);
    if (segment == NULL) return NULL;
    mi_segments_track_size((long)segment_size,tld);
  }
  memset(segment, 0, info_size);
  if (mi_option_is_enabled(mi_option_secure)) {
    _mi_os_protect((uint8_t*)segment + info_size, (pre_size - info_size) );
    size_t os_page_size = _mi_os_page_size();
    if (mi_option_get(mi_option_secure) <= 1) {
      _mi_os_protect((uint8_t*)segment + segment_size - os_page_size, os_page_size );
    }
    else {
      for (size_t i = 0; i < capacity; i++) {
        _mi_os_protect((uint8_t*)segment + (i+1)*page_size - os_page_size, os_page_size );
      }
    }
  }
  segment->page_kind  = page_kind;
  segment->capacity   = capacity;
  segment->page_shift = page_shift;
  segment->segment_size = segment_size;
  segment->segment_info_size = pre_size;
  segment->thread_id  = _mi_thread_id();
  segment->cookie = _mi_ptr_cookie(segment);
  for (uint8_t i = 0; i < segment->capacity; i++) {
    segment->pages[i].segment_idx = i;
  }
  mi_stat_increase(tld->stats->page_committed, segment->segment_info_size);
  return segment;
}
 
        再來看下釋放接口mi_free, 首先根據地址找到所在段, 再根據段找到頁. 如果釋放的內存是本線程分配的則掛入local free鏈表(如果整頁都空閑則還會嘗試釋放頁), 否則掛入thread free鏈表.
void mi_free(void *p) {
  const mi_segment_t *const segment = _mi_ptr_segment(p);
  if (segment == NULL) return;
  bool local = (_mi_thread_id() == segment->thread_id);
  mi_page_t *page = _mi_segment_page_of(segment, p);
  if (page->flags.value==0) {
    mi_block_t *block = (mi_block_t*)p;
    if (local) {
      mi_block_set_next(page, block, page->local_free);
      page->local_free = block;
      page->used--;
      if (mi_page_all_free(page)) { _mi_page_retire(page); }
    }
    else {
      _mi_free_block_mt(page, block);
    }
  }
  else {
    mi_free_generic(segment, page, local, p);
  }
}
mi_segment_t *_mi_ptr_segment(const void *p) {
  return (mi_segment_t*)((uintptr_t)p & ~MI_SEGMENT_MASK);
}
mi_page_t *_mi_segment_page_of(const mi_segment_t *segment, const void *p) {
  ptrdiff_t diff = (uint8_t*)p - (uint8_t*)segment;
  uintptr_t idx = (uintptr_t)diff >> segment->page_shift;
  return &((mi_segment_t*)segment)->pages[idx];
}
bool mi_page_all_free(const mi_page_t *page) {
  return (page->used - page->thread_freed == 0);
}
 
        5. 遺留問題與思考
通篇閱讀下來, 感覺mimalloc也沒有用到特別新穎的技術. 本質還是slab分配器, 那為什么性能能提升10%呢?
 直覺的感受是,
- slab的切片機制無需migrate內存, 減少碎片化, 同時降低了整體的管理開銷.
 - 將生產消費隊列區分開來, local線程代碼lock free, 提升並發性能.
 - 延遲釋放的機制, 類似於RCU的同步, 進一步降低資源競爭的損耗.
 - 至於論文里本身提到的局部性問題, 我很好奇怎么得出的結論, 是否有測試數據.
 
問題:
- bin與page對應關系?
 - OS接口還沒寫.
 - 有空補張圖吧, 感覺內存布局這塊寫的太亂了.
 
