mimalloc内存分配代码分析

这篇文章中我们会介绍一下mimalloc的实现,其中可能涉及上一篇文章提到的内容,如果不了解的可以先看下这篇mimalloc剖析。首先我们需要了解的是其整体结构,mimalloc的结构如下图所示

 

mimalloc整体结构

在mimalloc中,每个线程都有一个Thread Local的堆,每个线程在进行内存的分配时均从该线程对应的堆上进行分配。在一个堆中会有一个或多个segment,一个segment会对应一个或多个页,而内存的分配就是在这些页上进行。mimalloc将页分为三类:

  • small类型的segment的大小为4M,其负责分配大小小于MI_SMALL_SIZE_MAX的内存块,该segment中一个页的大小均为64KB,因此在一个segment中会包含多个页,每个页中会有多个块
  • large类型的segment的大小为4M,其负责分配大小处于MI_SMALL_SIZE_MAX与MI_LARGE_SIZE_MAX之间的内存块,该segment中仅会有一个页,该页占据该segment的剩余所有空间,该页中会有多个块
  • huge类型的segment,该类segment的负责分配大小大于MI_LARGE_SIZE_MAX的内存块,该类segment的大小取决于需要分配的内存的大小,该segment中也仅包含一个页,该页中仅会有一个块

根据heap的定义我们可以看到其有pages_free_direct数组、pages数组、Thread Delayed Free List以及一些元信息。其中pages_free_direct数组中每个元素对应一个内存块大小的类别,其内容为一个指针,指向一个负责分配对应大小内存块的页,mimalloc在分配比较小的内存时可以通过该数组直接找到对应的页,然后试图从该页上分配内存,从而提升效率。pages数组中每个元素为一个队列,该队列中所有的页大小均相同,这些页可能来自不同的segment,其中数组的最后一个元素(即pages[MI_BIN_FULL])就是前文提到的Full List,倒数第二个元素(即pages[MIN_BIN_HUGE])包含了所有的huge类型的页。thread_delayed_free就是前文提到的Thread Delayed Free List,用来让线程的拥有者能够将页面从Full List中移除。
 
struct mi_heap_s {
  mi_tld_t*             tld;
  mi_page_t*            pages_free_direct[MI_SMALL_WSIZE_MAX + 2];
  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;
};
在heap的定义中我们需要特别注意的一个成员是tld(即Thread Local Data)。其成员包括指向对应堆的heap_backing,以及用于segment分配的segment tld以及os tld。
struct mi_tld_s {
  unsigned long long  heartbeat;
  mi_heap_t*          heap_backing;
  mi_segments_tld_t   segments;
  mi_os_tld_t         os;
  mi_stats_t          stats;
};

typedef struct mi_segments_tld_s {
  // 该队列中所有的segment均有空闲页,由于large与huge类型的segment仅有一个页,因此该队列中所有segment均为small类型
  mi_segment_queue_t  small_free;
  size_t              current_size;
  size_t              peak_size;
  size_t              cache_count;
  size_t              cache_size;
  // segment的缓存
  mi_segment_queue_t  cache;
  mi_stats_t*         stats;
} mi_segments_tld_t;

typedef struct mi_os_tld_s {
  uintptr_t           mmap_next_probable;
  void*               mmap_previous;
  uint8_t*            pool;
  size_t              pool_available;
  mi_stats_t*         stats;
} mi_os_tld_t;

mi_malloc

首先要说明一下,所有贴出的源代码都可能会有一定程度的删减,例如一些平台相关的代码,一些用于信息统计的代码都可能被删去。接下来我们跟着mi_malloc来看一下内存分配的流程,其流程仅有两部,获取该线程拥有的堆,然后从这个堆上分配一块内存。
extern inline void* mi_malloc(size_t size) mi_attr_noexcept {
  return mi_heap_malloc(mi_get_default_heap(), size);
}

获取线程拥有的堆

首先介绍一下mimalloc有哪些堆,mimalloc会为每个线程保留一个Thread Local的堆,每个线程均使用该堆进行内存分配,除此之外还有一个全局变量_mi_heap_main,该堆会被主线程视为Thread Local的堆,由于某些OS会用malloc来进行Thread Local的内存分配,因此_mi_heap_main在mimalloc尚未初始化时也会被视作默认的堆来进行内存分配。

我们先来看一下mi_get_default_heap,该函数会直接返回一个Thread Local的_mi_heap_default,但是该Thread Local默认是被初始化为_mi_heap_empty,之后在调用mi_heap_malloc时如果发现该Thread Local并未初始化则会将其初始化为一个新的堆。
static inline mi_heap_t* mi_get_default_heap(void) {
#ifdef MI_TLS_RECURSE_GUARD
  if (!_mi_process_is_initialized) return &_mi_heap_main;
#endif
  return _mi_heap_default;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值