- 语言经验 - 《c++的高性能内存管理库tcmalloc和jemalloc》

         本文属于专栏《构建工业级QPS百万级服务》​​​​​        


 1、前置知识        

        c++的内存管理,主要说的是堆内存管理。现代计算机系统中,用户进程的堆内存,由内核映射。

  • 堆内存的来源
    •  主要是通过mmap()函数,在进程的虚拟地址空间中创建新的映射
  •  为什么需要malloc
    •  因为mmap()是按照page size(一般是4096字节)分配的,如果是程序员直接使用很容易照成浪费,高效地使用内存,是共同需求,所以有了malloc
  • 为什么需要tcmalloc和jemalloc
    •  malloc在内存碎片和多线程性能方面做得不好
  • 三方的内存管理库运行原理,就是alias malloc和free函数以及operator new(注意不是new operator)
#define ALIAS(tc_fn)   __attribute__ ((alias (#tc_fn), used))

extern "C" { 
  void* malloc(size_t size) __THROW               ALIAS(tc_malloc);
  void free(void* ptr) __THROW                    ALIAS(tc_free);
}   // extern 
  • 两个常用的内存分配算法
    •  Buddy allocation:连续的内存,分配时不断折半,直到刚好满足需要的内存
    • Slab allocation:将内存管理起来,分成很多个大小相同的小片,用一个管理类去记录内存的分配和释放

2、TCMalloc(Thread Cache Malloc)

  • 实现原理:
  • 首先,glibc中,内存分配相关的函数都是弱符号,TCMalloc定义了自己的函数并将其覆盖
    • - 语言经验 - 《c++的高性能内存管理库tcmalloc和jemalloc》_第1张图片
    • 主要思想是每个线程有自己的缓存,在自己线程缓存不够用时,找Central Heap
    • 内存分配时,不同大小有不同的策
      • 小内存:
        • 每个线程缓存有一个单链表,每个节点后面跟着的也是一个链表,如下图,calss0 位8bytes,class1为16bytes,class2为32bytes。当thread cache不够,就找central heap,当central heap不够,就找内核分配。如果Thread Cache的链太短,会频繁找去central分配(自旋锁),如果太长会浪费一些空间
        • tcmalloc_max_total_thread_cache_bytes参数表示的是在当前thread cache中,最多的缓存的不释放回central cache的内存。设置太低会导致在高qps的服务中,有频繁释放和申请小内存时,会和central cache交互频繁,导致加锁太多
        • 为了设置合适的free list(class 0 后面跟着的链表)长度,使用了慢启动算法来决定每个独立的free list的链表长度,频繁的申请会增加长度,频繁的释放会抑制长度
        • - 语言经验 - 《c++的高性能内存管理库tcmalloc和jemalloc》_第2张图片
      • 中内存CentralCache(256kb <= size <= 1MB)
        • 这里的个page为8kb
        • - 语言经验 - 《c++的高性能内存管理库tcmalloc和jemalloc》_第3张图片
      • 大内存PageHeap,从pageHeap中找,如果没有就向OS申请
        • - 语言经验 - 《c++的高性能内存管理库tcmalloc和jemalloc》_第4张图片
      • 调优化经验
        • 对于应用使用堆内存量大的,如几个G甚至上百G的。虽然版本不同,但是一般给每个线程默认管理的内存大小不超过100M。所以在高配置应用中,使用TCMALLOC_MAX_TOTAL_THREAD_CACHE_BYTES来增加单个线程可管理的内存是有显著性能提升的。如:export TCMALLOC_MAX_TOTAL_THREAD_CACHE_BYTES=1073741824

3、JeMalloc

  • 与tcmalloc类似,只是中内存不是从central heap中取,还是从线程的缓存中取,jemalloc的线程缓存管理范围更广,更复杂,但是效率更高
  • 原理
    • 几个名词
      • page ,操作系统提供的内存,来自于mmap
      • chunk,jemalloc申请内存的大小单位,是page size的n倍,默认2Mb
      • base,jemalloc自身使用的堆内存结构
      • arena,jemalloc最重要的部分,内存管理器(每个线程一个),分配算法是Buddy和Slab的组合
        • chunk 使用buddy算法划分不同的run,run使用不同的算法划分固定大小的region,大部分内存分配直接查找对应的run,从中分配空闲的region
      • bin:bin管理相同类型的run,记录了run相关信息。用红黑树管理有空闲的region的run,并按照地址排序
    • 分配流程
      • small内存去bin管理的内存中找,bin找不到再去arena管理的内存找
      • large的直接去arena中找
      • huge也是去arena找,只是这里用的是线程共享的arena,但是这里频率很低,影响小

          所以一般来说,频繁创建和删除线程的使用tcmalloc,用固定线程的使用jemalloc效率更高。

你可能感兴趣的:(构建工业级QPS百万级服务,c++,开发语言)