Malloc技术原理解析以及在转转搜索业务上的实践( 七 )

  • region cache:用于大于hugepage 大小的内存申请 , 这个cache 允许分配多个连续的hugepage;
  • hugepage cache:和region cache的功能有点重复 , 也是用于分配大于hugepage 的内存申请 。
  • 4.2 Per-thread modeper-thread的cache模式是TCMalloc 名字 Thread-Cacheing malloc的由来 。小内存的申请和释放会根据thread-cache的需求在middle-end 之间迁移 。
    每一个 thread-cache 内部不同size-class 对象会各自构造一个单链表(如果有 n 个size-classes , 也就会有对应 n 个单链表) , 类似如下图:
    Malloc技术原理解析以及在转转搜索业务上的实践

    文章插图
    图片
    分配某一个对应size-class 对象的时候 , 对应 size-class 链表对象会被从单链表移除(free-list) , 表示这个指针对应地址的内存可以被用户使用 。释放对象的时候 , 则会将这个对象地址追加到thread-cache 管理的 size-class 的链表 。在这个过程中 , 如果thread-cache 管理的内存不够 , 或者超限 , 则会从 middle-end 获取更多的内存对象或者将多余的内存对象释放给 middle-end 。
    对于per-thread caches来说 , 可以通过参数设置最大的可用内存大小 。每一个线程有自己的最小的thread-cache大小 512K , 如果当前线程内存申请需求较大 , 内存容量也会通过middle-end 将其他线程的可用内存迁移到当前线程 。通过 middle-end 来协调当前的thread-cache 内存 , 通过ThreadCache::Scavenge()进行 。如果当前线程退出 , 则会将自己的thread-cache 的内存返回给 middle-end 。
    Per-thread 模式下 , cache 内部的最大存储对象容量 达到当前最大阈值时就会从middle-end 获取更多的对象 , 从而增大这个限制 。降低最大限制的前提是发现了较多的未被使用的对象 , 则会将这一些对象根据需求还给middle-end 。
    4.3 Per-CPU mode然而这种场景会随着用户线程的大量增加 , 出现了一些内存问题:每个线程只能有极小的thread-cache , 需要消耗较多的CPU资源来聚合每个线程的内存资源 。新的per-CPU 模式应运而生 , 这种模式下每一个逻辑CPU 会有自己的的thread-cache 用来为运行在这个cpu的现场分配内存 。
    Per-cpu mode和per-thread mode是tcmalloc的font-end 主体部分的两种模式 。因为per-thread mode受到系统进程的线程数的影响 , 在大量线程的情况下会让每个thread-cache 只能够处理一小部分的内存申请释放需求 , 还会消耗大量的cpu 来 由middle-end 进行不同thread-cache 之间的内存迁移 。
    所以 tcmalloc 提供了优化版本的 per-cpu mode , 即每一个逻辑核维护一个 cpu-cache , 用来处理运行在当前核的线程的内存申请/释放需求 , 大体形态如下:
    Malloc技术原理解析以及在转转搜索业务上的实践

    文章插图
    图片
    Per-cpu mode下会申请一个大内存块(也可以称为slab) , 这个slab 会被多个cpu共享 , 其中每一个cpu会持有slab 的一部分内存 , 并在其上存储一系列元数据管理对应线程的内存对象 。
    上图中的cpu1会管理 on slab 的绿色部分内存区域 , 在这一部分区域中会先存储一些元数据和指针来管理不同大小的 size-classes 的内存对象 。其中元数据中包含一个header指针和每一个size-class 的索引block 。
    每一个size-class 的header 指针数据结构会有指向某一种实际存储内存对象的指针数组 , 即是一个数组 , 每一个元素是一个指针 , 总共是三个指针 , 分别指向这一种size-class 内存对象区域的起始地址块 , 当前地址块(后续分配这个size-class 大小对象的时候会从current 开始分配) , 最大地址 。
    每一个cpu 能够缓存的内存大小是通过SetMaxPerCpuCacheSize 配置的 , 也就是设置当前font-end 能够缓存的内存总大小取决去当前系统的cpu核心数 , 拥有更好核心数的机器使用tcmalloc 能够缓存更多的内存 。当每一个cpu cache 的内存被分配耗尽 , 想要从 middle-end 获取内存来缓存更多的对象时 , 也需要考虑对size-class进行扩容 。如果这个size-class 的内存分配需求还在持续增加 , 则这个size-class的容量会持续增加 , 直到达到这个size-class 容量的hard-code 。
    Per-cpu 模式下 , 增加cache 容量的前提是当前cache 是否在频繁的从 middle-end 获取内存 以及 释放内存交替 , 则需增加容量限制 , 有更多的空间来缓存这一些内存对象 。降低容量限制的前提是发现有一些空闲容量长时间没有被使用 。


    推荐阅读