Jacobwpeng.github.io

Github Pages


Project maintained by jacobwpeng Hosted on GitHub Pages — Theme by mattgraham

TCMalloc

1.简介


2.小内存申请


  1. 判断当前线程是否已经初始化了私有分配区
  2. 根据请求大小找到对应的SizeClass, 然后通过SizeClass找到对应的FreeLis t, 判断FreeList中是否有空闲内存块
  3. 当FreeList中没有空闲内存块时,向中央分配区申请一定数量(N)的这个SizeClass所代表的内存块
    • 1. 对中央分配区加锁
    • 2. 查看中央分配区中SizeClass对应的CentralFreeList中空闲内存块是否超过N
    • 3. 如果CentralFreeList中的内存块不足时,根据SizeClass计算申请向PageHeap申请一定数量(npage)的页.
    • 4. 对PageHeap加锁
    • 5. PageHeap首先查找npage对应的大小对应的SpanList,查看是否有能够直接满足这个需求的span存在
    • 6. 若找不到合适的span则继续向更大的SpanList中查找,找到后对大span做切割并放到对应的FreeList中
    • 7. 若还是找不到则向系统申请内存, 添加对应到SpanList中
    • 8. 将从PageHeap申请的span按照SizeClass所代表的内存块大小进行切割,添加到对应的CentralFreeList中, 同时将span加到该CentralFreeList中的non_empty_span_list_中, 在PageHeap中设置span对应的SizeClass
    • 9. 将申请到的内存块返回给线程私有分配区
  4. 返回一个刚申请的内存块,malloc调用返回

3.大内存申请


  1. 根据请求大小计算需要申请多少个页(N)
  2. 对PageHeap加锁
  3. 向PageHeap申请N个页, 返回包含这些页的spanm, 在PageHeap中设置span对应的SizeClass
  4. 返回刚申请到的span,malloc调用返回

4.内存释放(free)


  1. 首先根据释放的指针判断指针所属的PageID(ptr >> kPageShift)
  2. 根据PageID从PageHeap中找到所属的SizeClass
  3. 如果SizeClass属于大内存,那么直接调用PageHeap的Delete函数来回收这个Span, free返回
  4. 如果SizeClass属于小内存, 那么将这个内存块缓存在线程私有分配区中对应的FreeList中
  5. 判断本线程缓存的内存块总和大小size_是否超过本线程的缓存上限max_size_
  6. 如果超过则遍历FreeList,根据每个FreeList的low water mark来判断需要归还多少个内存块给中央分配区,然后增加本线程的max_size_,防止非常活跃的线程调用free频繁触发内存块归还操作
  7. 判断FreeList是否过长,如果过长,则返回一个batch_size的内存块给对应的CentralFreeList
  8. free返回

5.Span


Span代表了一块连续的内存页

Span一般通过双链表方式保存在CentralFreeList的non_empty_或者empty_

6.中央分配区管理


中央分配区实际上是一个CentralFreeList对应的数组,每个SizeClass都会对应一个CentralFreeList

CentralFreeList

关键成员
关键操作

7.PageHeap


PageHeap直接管理从OS申请过来的内存,并提供Page级别的内存分配,同时在内部维护PageID到Span的映射关系 调用PageHeap的Delete函数时并不会直接将内存返回给OS,而是缓存在对应的SpanList中,并增加已经释放的内存页总大小,如果总大小超过阈值时尝试从SpanList归还一些内存给OS

关键成员
关键操作

8.细节设计

9.概念

  1. CentralCache(中央分配区)

    • 线程共享
    • 操作加锁 (SpinLock)
  2. ThreadCache(线程私有分配区)

    • 使用TLS(Thread-Local Cache)保存分配区
    • 操作不加锁
    • 保存FreeList数组, 每个线程最多缓存4MiB, 最少512KiB, 所有线程最多32MiB
  3. SmallChunk

    • 小于 256KiB的内存块
    • 能够被ThreadCache缓存的最大内存块
  4. LargeChunk

    • 以Page为单位进行分配
    • 从Span中分割
  5. Page

    • 8KiB
    • 每次分配的最小单位
    • 由PageHeap管理
    • 每一个Page对应一个PageID
  6. Span

    • 连续的Page
    • PageHeap管理的最小单位
    • 分为 IN_USE, NORMAL_FREE, RETURNED_FREE
    • 每个Span对应一个PageID区间
    • 每个Span可能被用来切割为一堆更小的内存块,或者整体作为一块大内存时用
  7. PageHeap

    • 最底层的内存分配区,直接从OS申请内存并进行管理
    • 内部由几个SpanList组成
    • 通过PageMap保存PageID到Page对应的Span
    • 通过PageMapCached缓存PageID到Page对应切割成的
  8. SpinLock

    • 自旋锁
    • 用户态忙等while (flag) continue;
    • 适用于临界区很短,处理很快的场景
  9. SizeClass

    • 内部分配的小内存块大小类型
    • 根据用户申请的内存块像上取最接近的块