最近学习了tcmalloc机制,它是go里面用到的内存分配机制。本文参考tcmalloc,加上一部分自己的理解。

tcmalloc VS ptmalloc(glibc 2.3 malloc)

  对于小内存来说,tcmalloc提供线程级别的内存分配,这样就减少了线程之间的竞争,ptmalloc2也提供线程级别分配,但是它的内存被分配到某个线程后就不能重新分配给别的线程,这造成了较大的资源浪费。对于大内存来说,tcmalloc也采用了细粒度且高效的分配策略。
  在2.8 GHz P4环境下,tcmalloc执行小内存malloc/free的时间大约为50ns,小于ptmalloc2的300ns。
  另外在空间利用上,tcmalloc额外空间比较少,N个8字节的对象占用的总空间大概为8N*1.01,而ptmalloc2用4个字节管理每个对象,造成的空间利用率较小。

小内存分配和回收

  线程维护了一个cache,用于小内存分配,当需要申请内存时,先从线程cache中进行分配,如果不够再从Central Heap的central free lists进行分配;此外,tcmalloc还会执行定期垃圾回收,将线程cache中过多的内存进行回收,放入central free lists中。

  tcmalloc有大约88种大小的内存块(size-class),如果不足一块大小则会造成一定的碎片浪费,但是由于tcmalloc不是完全按2的指数倍进行分配的,所以浪费的空间不会很大。举个例子,内存块大小有8, 16, 32, 48, 64, 80,那么如果申请空间为70,分配块大小为80的即可,而不需要分配128造成较大的碎片浪费。所有的空闲块(class)以单链表进行维护,那么用到时直接从对应链表中取即可。

大体分配策略如下:

  1. 根据申请的大小,找到对应的块大小size-class。
  2. 如上图所示,从线程缓存链表中寻找对应块大小的空闲内存块
  3. 如果链表为非空,则直接返回,由于是线程内部没有竞争,所以不需要加锁。

如果链表为空,则

  1. 从central free lists中找到对应size-class的链表空闲块(结构跟上图线程缓存中链表一致)
  2. 将从central free lists空闲链表中找到的空闲块放入到线程缓存的链表中
  3. 从线程缓存的链表中返回一个

如果central free lists中对应size-class的链表也为空,则

  1. 则分配一个页
  2. 然后将该页划分出多个需要的size-class的块
  3. 将块放入central free lists空闲链表
  4. 将central free lists空闲链表空闲块一部分移入到线程缓存的链表中
  5. 从线程缓存的链表中返回一个

  这种多级分配的好处在于,第一级是线程内部资源,不需要进行加锁抢占影响性能。
  那么线程内部链表的长度到底应该为多少,如果小了将会导致不断从Central free list中进行申请,造成竞争;如果大了则会造成浪费。分配策略采取了慢启动增长的方式,防止系统因为大批量申请分配造成抖动,伪代码如下。

    Start each freelist max_length at 1.Allocationif freelist empty {fetch min(max_length, num_objects_to_move) from central list;if max_length < num_objects_to_move {  // slow-start, 每次只+1max_length++;} else { // 如果超过num_objects_to_move则快增长max_length += num_objects_to_move;}}Deallocationif length > max_length {// Don't try to release num_objects_to_move if we don't have that many.release min(max_length, num_objects_to_move) objects to central listif max_length < num_objects_to_move { // 尝试继续增长max_length长度// Slow-start up to num_objects_to_move.max_length++;} else if max_length > num_objects_to_move {// If we consistently go over max_length, shrink max_length. 计数策略,只有计数超过给定阈值,才会对max_length进行收缩,同样防止抖动。overages++;if overages > kMaxOverages {max_length -= num_objects_to_move;overages = 0;}}}

中内存分配

  中内存指的是大小为256KB到1MB的大小,将会分配多个页,Central Heap以8k为一个页。Central Heap有一个包含128个链表的heap,链表大小从1个页到128个页。那么,如果申请的空间需要多少个page则从heap对应项中的链表中取出一个,如果该链表为空,则从下一个链表寻找(将会导致重新划分,比如需要4个页,但是4 pages链表为空,则从5pages中拿一个,但是5pages应该拆分为2部分,一个为4page,一个为1page)。如果都不满足需求,则视为“大内存”分配。

  注意,中内存不是从线程缓存中分配,而是直接在CentralHeap。

大内存分配

  大内存指的是大于1MB的块,这个将会从红黑树中进行分配。tcmalloc维护了一个红黑树用于标记不同的span,如果需要则从树种找到一个满足条件的最小的span进行分配。这可能导致span进行重新划分,一部分用于分配申请的大小,另外多出的部分可能重新放入到红黑树,也可能放入全局Centeral Heap的center free-lists(上图所示)。如果红黑树中找不到满足的span,则从系统中进行申请(tcmalloc采用sbrk和mmap)

Span

  span对象负责管理连续的多个页,比如下图,a,b,c,d是4个span,管理不同大小的连续页。

  所有span组成一个span lists,span内部划分给多个对象使用,参加下图,图来自图解 TCMalloc

  一个span可能被分配或者释放。如果释放了,挂入到page heap的链表中。如果申请了,可能作为大内存被分配,也可能被划分后进行分配,比如划分为多个小内存。如果被划分成小内存,则内部会记录各个size-class的分配情况。
  同样还是上面第一个图,可以用一个数组记录各个页分别属于哪个span。但是这样对空间消耗比较大,每一块内存都需要一个数组元素表示,tcmalloc用基数radix tree(前缀树/字典树trie tree的一种压缩优化)记录映射情况,这样可以对前缀相同部分进行压缩。如果是32位系统,则树高2层,根节点包含了32项,叶子节点包含2^14项,则对32位系统来说,可以记录2^19个页(2^19 * 8k=2^32)。如下图所示,采用基数而不是前缀树的原因是为了空间压缩,如果是前缀树,则需要2^32 * 2 - 1=2^33-1个结点,就算结点内只有一个值和一个指针,存储这么多指针也需要浪费很多的空间。下图是手绘的radix tree示意图。

  对于64位系统,则树高3层。

回收

  当一个对象被释放后,通过计算页号,再通过radix tree得知所在的span,span可以知道是否为小内存,如果是的话还知道size-class(span内部维护该信息)。当然,在开始的时候说过,如果是小内存,释放会挂入线程内部的缓存链表,知道链表大小超过了阈值则会回收到central free lists。当然central free lists也可能发生回收,属于一个span的内存全部空闲后,将会触发回收。
  如果是大内存,则通过span获取页跨度,假设为[p,q],那么寻找p-1和q+1所在的span是否为空,如果都为空,则进行合并后放入page heap(上面提到的红黑树)。

central free lists

  上面我们提过,我们在central free lists中保存了不同size-class小内存用于二次分配。每个central free lists由二级结构组成,第一级是span,第二级是span内空闲空间组成的链表,保存小内存地址。如果线程内部小内存不够用,就从这里进行分配。如果线程内部小内存过多就行回收,也挂入这里的链表。如果一个span内的内存全都回收后,该span也将进行回收,放入到全局的page heap中。

线程缓存的垃圾回收

  在开始讲小内存的伪代码中已经介绍过垃圾回收的机制。当线程缓存超过max_size以后,将会触发回收。垃圾回收只有在对象释放的时候才会发生。用户可以通过控制tcmalloc_max_total_thread_cache_bytes来控制num_objects_to_move的大小。每次垃圾回收,线程将尝试将自己的max_size变大。如果回收时max_size小于tcmalloc_max_total_thread_cache_bytes,那么max_size将会直接增长;反之,则会促使从别的线程中偷取max_size。
此处存在一个疑问:这种机制将会导致max_size超过tcmalloc_max_total_thread_cache_bytes,和上面伪代码中的收缩机制有什么区别?什么时候用收缩,什么时候从别的线程进行偷取?

说明

转载请注明链接:http://vinllen.com/tcmallocqian-xi/

参考:

https://gperftools.github.io/gperftools/tcmalloc.html
https://github.com/gperftools/gperftools/blob/master/src/tcmalloc.cc
https://zhuanlan.zhihu.com/p/29216091

tcmalloc浅析相关推荐

  1. 图解tcmalloc内存分配器

    目录 前言 如何分配定长记录? 如何分配变长记录? 大的对象如何分配? Span如何分配? 从Page到Span PageHeap 全局对象分配 ThreadCache 总结 参考 推荐阅读 前言 T ...

  2. 内存优化总结: ptmalloc、tcmalloc 和 jemalloc

    这里填写标题 1. 内存优化总结: ptmalloc.tcmalloc 和 jemalloc 1.1. tcmalloc, jemalloc 和 ptmalloc 对比 1.2. 需求 1.3. 目标 ...

  3. 内存优化总结:ptmalloc、tcmalloc和jemalloc

    概述 需求 系统的物理内存是有限的,而对内存的需求是变化的, 程序的动态性越强,内存管理就越重要,选择合适的内存管理算法会带来明显的性能提升. 比如nginx, 它在每个连接accept后会mallo ...

  4. 【性能】tcmalloc 使用和原理

    目录 一. 安装 二. 使用 使用方法 对比测试 替换内建的malloc/free 三.原理 四.问题或质疑 为什么测试的TCMalloc不靠谱,性能反而差了 tcmalloc是尬尴的存在? 五.其他 ...

  5. 图解 TCMalloc

    来源:https://zhuanlan.zhihu.com/p/29216091 前言 TCMalloc 是 Google 开发的内存分配器,在不少项目中都有使用,例如在 Golang 中就使用了类似 ...

  6. 几种常用内存管理底层介绍

    需求 系统的物理内存是有限的,而对内存的需求是变化的, 程序的动态性越强,内存管理就越重要,选择合适的内存管理算法会带来明显的性能提升. 比如nginx, 它在每个连接accept后会malloc一块 ...

  7. 浅析 JavaScript 中的 函数 uncurrying 反柯里化

    柯里化 柯里化又称部分求值,其含义是给函数分步传递参数,每次传递参数后部分应用参数,并返回一个更具体的函数接受剩下的参数,这中间可嵌套多层这样的接受部分参数函数,直至返回最后结果. 因此柯里化的过程是 ...

  8. 浅析Python中bytes和str区别

    本博转载自:Chown-Jane-Y的浅析Python3中的bytes和str类型 Python 3最重要的新特性之一是对字符串和二进制数据流做了明确的区分.文本总是Unicode,由str类型表示, ...

  9. 学习《Linux设备模型浅析之设备篇》笔记(深挖一)

    这篇文章既然说了是浅析,那就是跳过了一些东西,我们把这些跳过的东西给它尽可能的补回来 今天登陆 lxr.free-electrons.com 发现内核版本已经升级到3.15了,那以后都使用3.15的源 ...

最新文章

  1. 【NOIP2015提高组Day1】 神奇的幻方
  2. Use Chunks.groupsIterable and filter by instanceof Ent rypoint instead
  3. 【pytorch】pytorch-yolov3拍照并保存,进行检测后遍历所有图片并显示图片
  4. 【Qt】进程间通信之QSharedMemory示例
  5. Apicloud开发之V7包继承AppCompactActivity后云编译资源找不到的解决办法
  6. 量子计算机 计算混沌,深入了解量子混沌可能是量子计算机的关键
  7. 笨办法学 Python · 续 练习 2:创造力
  8. VB.NET工作笔记007---ASP.NET中Session超时一直不起作用
  9. 【Linux】makefile文件基础
  10. C语言之父辞世引发“分号”悼念
  11. 鄙视那些把爬虫当作AI的SB,清华学霸尹成大哥的历史上最强大的爬虫视频
  12. 修改mysql的authen_MySQL数据库出现Authentication plugin怎么办
  13. ffmpeg常用操作 - 录屏 - 转码
  14. Js获取昨天今天明天的日期
  15. Mac升级python3版本
  16. [教程]配置青鸟云Web服务器
  17. 华三防火墙配置端口地址转换_H3C SecPath 防火墙设置之端口映射(命令)
  18. 【C语言】求一个四位整数各位数字之和
  19. [S]O-10-2 青蛙跳台阶问题
  20. 常用网络ip地址有哪些

热门文章

  1. MySQL尚硅谷笔记
  2. 【Unity Shaders】Diffuse Shading——创建一个基本的Surface Shader
  3. 【CAD二次开发】-ObjectARX-扩展数据 (Xdata)
  4. 直接import carla
  5. 物联网工程专业的迷茫与抉择
  6. 《泰囧》的票房是如何成功运营的
  7. 《软件工程之美》—— 目录
  8. 实时音频混音技术在视频直播场景中的实践
  9. 学习Linux从什么地方下手,如何获得帮助 ── 《LinuxSir初学者指北》
  10. 【Python习题】房贷计算器