0. 概述

在曾经的一篇文章:malloc 和 free 的底层实现 中,我介绍过 GLibc 的 malloc 实现思路,但是,很多使用 C/C++ 的程序底层的动态内存管理都不用 Glibc 的 malloc 和 free,而是选择其他的 malloc 或者干脆自己实现。其中,Google 也是如此,因为它发现 Glibc 的 malloc 并不是那么适合自己的应用场景,于是就实现了自己的 TCMalloc,这里我就来学习一下关于 TCMalloc 的知识。

1. 概念

和 Glibc 的 malloc 一样,TCMalloc 也是有一些内部结构的,分别是:

2. 内存组织形式

在 TCMalloc 中,内存可能被逻辑上分为两部分,分别是 Thread Cache 和 Central Heap:

在物理上内存划分为小内存,中内存和大内存,划分的标准为:

其中小内存会存在与 Thread Cache 和 Central Heap 中,并且在这中逻辑概念中的物理存放形式还不一样,在 Thread Local 中,存放是以 size 范围来存放的:

这里分为 88 个等级,小区间以 8 bytes 为间隔,然后大一点的空间以 16 bytes 为间隔,然后再大一点就 32 bytes,依此类推,类似于:

class 块大小
0 0-8
1 8-16
..
- 961-1024

这样,需要一块内存的时候,直接上取整找对应的链表来分配。这是在 Thread Local 中的形式,对于在 Central Heap 中,就多了一层 Span,也就是说,每个级别的第一层的 Linked List 是 Span,然后对于 Span 内部还维护这一个小内存的 Linked List。

对于中型大小的内存,则只有在 Central Heap 中存在,他的组织形式是以 page 为单位,在 Central Heap 中有一个 128 长度的数组,每隔数组对应这 1.2.3…128 个 page 的内存块:

对于大内存,也是直接放在 Central Heap 中,是以红黑树的形式有序的存放。除此之外,TCMalloc 还在 Central Heap 中以 Radix Tree 的形式存放了 Page 的 Span 信息。

在 32位系统中,是两层结构,第一层是包含 32 个元素,然后第二层的每隔叶子节点包含 214 个元素,因为 32 位系统可以划分为 219 个 8K 内存,所以每个叶子的大小是 214,因此 TCMalloc 运行的时候 Central 就需要提前浪费掉 64 KB(214 * 4 byte)内存(why?没有很理解)。

如果在 64 位系统中,会采用 3 层的 Radix Tree。

3. 内存分配

当理解完 TCMaclloc 的内部内存存放之后,对于内存分配就比较简单了,对于小内存,分配的步骤为:

  1. 根据需要的内存大小,先确定分配的 class,然后从 Thread Local 中查找,如果有,那么就直接用
  2. 如果在 Thread Local 中没有合适的大小,那么就从 Central Heap 中对应的 class 链表中获取一部分过来,分配一个来用,其他放回 Thread Local 的链表
  3. 如果 Central Heap 中对应 class 的链表上也没有,那么就从 Central heap 中获取一些 Page,然后再划分为对应大小的 Object,先放到 Central Heap 中对应 class 的链表上,再给一部分 Thread Local

对于中块大小的内存,因为只在 Central Heap 中存放,那么获取起来就简单一些了:

那么大内存的分配策略又是如何:

如果大内存中找到的空闲块比实际需要的大,那么可以对大内存进行切分,切分出来的空闲块根据他的大小,放会到对应的(大内存/中型内存/小内存)存储结构中。这就是整个的分配策略。

4. 内存释放

有借有还,再借不难,既然分配了,那么还回来要怎么处理也是值得学习的。这里也要区分内存的大小,如果是小内存,那么就直接放回 Thread Local 的对应的 class 的链表中,如果 Thread Local 中的小内存数量超过一定限制(2 MB),那么就需要执行依次垃圾回收(下一级)了。

如果是中型内存,没有太复杂的操作,直接放到 Central Heap 对应的 page 索引的链表中,如果是大块内存([p, q],那么在放到 Central Heap 的红黑树中之前,还会确认一下前后的 page(p - 1 和 q + 1) 是否空闲,如果有空闲,那么先合并一波再放回 Central Heap 中。

5. 垃圾回收

当 Thread Local 中,小内存数量太多(超过 2MB)之后,TCMalloc 就会选取一些还给 Central Heap,选取的标准为每个 Thread Local 都有一个链表最短长度 L,超过 L 长度的内存都会还给 Central Heap,如果都不超过 L 且总内存还太大,那么就减小一半 L(L= L/2),再进行一遍。

当小内存从 Thread Local 还回去 Central Heap 中的 Span 中的链表之后,如果对应 Span 链表中的空闲数(链表长度)等于 Span 被划分的块数,那么这个 Span 就会被还回给对应的 Page 数组(因为它已经是空闲的了)。

6. Ref