全局内存分配器:tcmalloc_sys_alloc

来源:互联网 发布:淘宝网店上货教程 编辑:程序博客网 时间:2024/04/30 07:01

[TCMalloc] 全局内存分配器

  TCMalloc 的全局分配器,处于 TCMalloc 的最底层,负责向操作系统申请和释放内存,接口有两个,定义在 src/system-alloc.h|.cc:

extern void* TCMalloc_SystemAlloc(size_t bytes, size_t *actual_bytes,                                  size_t alignment = 0);extern bool TCMalloc_SystemRelease(void* start, size_t length);

  TCMalloc_SystemAlloc 的参数,除了要申请的大小 bytes,还有 actual_bytes 和 alignment,因为对齐的需求,该接口可能分配大于 bytes 的内存,实际大小保存在 actual_bytes。TCMalloc_SystemRelease 负责『释放』内存,至于释放为什么加引号,下面会提到。接下来,介绍下这两个接口的实现。

  malloc-extension.h 里面,定义了接口类 SysAllocator,该类只有一个接口:

class SysAllocator { public:  SysAllocator() {}  virtual ~SysAllocator();  virtual void* Alloc(size_t size, size_t *actual_size, size_t alignment) = 0;};

  system-alloc.cc 里面,对 SysAllocator 有三个实现:SbrkSysAllocator, MmapSysAllocator, DefaultSysAllocator。SbrkSysAllocator 和 MmapSysAllocator 分别使用 sbrk 和 mmap 向系统分配内存,这两个类没有任何数据成员。DefaultSysAllocator 是 前两种 Allocator 的委托:

class SbrkSysAllocator : public SysAllocator {public:  SbrkSysAllocator() : SysAllocator() {  }  void* Alloc(size_t size, size_t *actual_size, size_t alignment);}; class MmapSysAllocator : public SysAllocator {public:  MmapSysAllocator() : SysAllocator() {  }  void* Alloc(size_t size, size_t *actual_size, size_t alignment);}; class DefaultSysAllocator : public SysAllocator { public:  DefaultSysAllocator() : SysAllocator() {    for (int i = 0; i < kMaxAllocators; i++) {      failed_[i] = true;      allocs_[i] = NULL;      names_[i] = NULL;    }  }  void SetChildAllocator(SysAllocator* alloc, unsigned int index,                         const char* name) {    if (index < kMaxAllocators && alloc != NULL) {      allocs_[index] = alloc;      failed_[index] = false;      names_[index] = name;    }  }  void* Alloc(size_t size, size_t *actual_size, size_t alignment); private:  static const int kMaxAllocators = 2;  bool failed_[kMaxAllocators];  SysAllocator* allocs_[kMaxAllocators];  const char* names_[kMaxAllocators];};static char sbrk_space[sizeof(SbrkSysAllocator)];static char mmap_space[sizeof(MmapSysAllocator)];static char default_space[sizeof(DefaultSysAllocator)];

  在 libtcmalloc 初始化的时候,使用 new 操作符的 placeholder 形式,分别在 sbrk_space, mmap_space, default_space 上面初始化这三个 Allocator。然后调用 DefaultSysAllocator 的 SetChildAllocator 接口,将 SbrkSysAllocator 和 MmapSysAllocator 设置成待用分配器:

void InitSystemAllocators(void) {  MmapSysAllocator *mmap = new (mmap_space) MmapSysAllocator();  SbrkSysAllocator *sbrk = new (sbrk_space) SbrkSysAllocator();  DefaultSysAllocator *sdef = new (default_space) DefaultSysAllocator();  if (kDebugMode && sizeof(void*) > 4) {    sdef->SetChildAllocator(mmap, 0, mmap_name);    sdef->SetChildAllocator(sbrk, 1, sbrk_name);  } else {    sdef->SetChildAllocator(sbrk, 0, sbrk_name);    sdef->SetChildAllocator(mmap, 1, mmap_name);  }  sys_alloc = tc_get_sysalloc_override(sdef);}

  DefaultSysAllocator 的 Alloc 接口是这样实现的:

void* DefaultSysAllocator::Alloc(size_t size, size_t *actual_size,                                 size_t alignment) {  for (int i = 0; i < kMaxAllocators; i++) {    if (!failed_[i] && allocs_[i] != NULL) {      void* result = allocs_[i]->Alloc(size, actual_size, alignment);      if (result != NULL) {        return result;      }      failed_[i] = true;    }  }  // After both failed, reset "failed_" to false so that a single failed  // allocation won't make the allocator never work again.  for (int i = 0; i < kMaxAllocators; i++) {    failed_[i] = false;  }  return NULL;}

  先后尝试使用 SbrkSysAllocator 和 MmapSysAllocator 分配内存,如果某个分配器分配失败,则标记相应的 failed_ 域,下次便不从这个分配器分配内存。如果全部失败,则返回 NULL,同时清除 failed_ 域,以免无法恢复。TCMalloc_SystemAlloc 函数分配内存时候,调用全局的 sys_alloc->Alloc() 即可。
  关于全局内存的分配要介绍的就这么多,至于 SbrkSysAllocator 和 MmapSysAllocator 的 Alloc 实现细节,但凡对 sbrk 和 mmap 了解的人一猜就知道了。
  下面介绍下内存的释放。可以注意到,前面的 Allocator 只有 Alloc 接口,并没有一个 Release 或者 Free 接口,那么内存是怎么释放的呢?事实上,TCMalloc 从来不释放内存!
  那么,TCMalloc_SystemRelease 做些什么呢?我们知道,无论是 brk,还是 mmap,申请的都是虚拟内存,物理内存是由内核在缺页中断时按需分配的。TCMalloc_SystemRelease 并没有调用 sbrk 和 munmap 将虚拟内存『归还』给操作系统,而是调用 madvise 系统调用。

#if !defined(MADV_FREE) && defined(MADV_DONTNEED)# define MADV_FREE  MADV_DONTNEED#endifbool TCMalloc_SystemRelease(void* start, size_t length) {#ifdef MADV_FREE  const size_t pagemask = pagesize - 1;   size_t new_start = reinterpret_cast<size_t>(start);  size_t end = new_start + length;  size_t new_end = end;  // Round up the starting address and round down the ending address  // to be page aligned:  new_start = (new_start + pagesize - 1) & ~pagemask;  new_end = new_end & ~pagemask;   if (new_end > new_start) {    int result;    do {      result = madvise(reinterpret_cast<char*>(new_start),          new_end - new_start, MADV_FREE);    } while (result == -1 && errno == EAGAIN);     return result != -1;  }#endif  return false;}

  因为 Linux 不支持 MADV_FREE,所以使用了 MADV_DONTNEED。使用 MADV_DONTNEED 调用 madvise,告诉内核这段内存今后『很可能』用不到了,其映射的物理内存尽管拿去好了!
  因此,TCMalloc_SystemRelease 只是告诉内核,物理内存可以回收以做它用,但虚拟空间还留着。
  那么,是不是 TCMalloc 永远不会归还虚拟空间呢?一般来说,是的。但当虚拟空间耗尽,或者虚拟空间有碎片,无法满足内存需要时,TCMalloc 会试图合并,然后把所有空闲的虚拟空间归还,然后从新分配。

原创粉丝点击