当前位置: 首页 > news >正文

Tcmalloc内存分配算法的分析

一、介绍

tcmalloc是Google搞的一个内存管理算法,据说分配速度要比glibc自带的malloc快很多。但是客观分析来说,只是在某些场景下是如此。tmalloc具有现代内存管理器的特点,可以减少碎片,支持多CPU的扩展等等。它的应用还是比较多的,比较有名就是Golang语言中就使用类似的算法。
另外,在比较复杂的多线程环境下,tmalloc也对相关的锁的粒度进行了细化,并且引入了自旋锁,可以更好的处理在多线程情况下的竞态。对于一些很小的内存对象,几乎不会涉及到竞态的情况。另外,tcmalloc对小对象的存储表示有着更节省空间的方式,相对pmalloc来说正如网上描述:
“N 8-byte objects can be allocated while using space approximately 8N * 1.01 bytes. I.e., a one-percent space overhead. ptmalloc2 uses a four-byte header for each object and (I think) rounds up the size to a multiple of 8 bytes and ends up using 16N bytes.”

二、算法说明

说到内存分配,其实就涉及到两个问题:
1、线程内的分配(串行分配)
2、多线程的分配(并行分配)
而具体到内存的分配控制,又可以分为大内存对象分配和小内存对象分配。当然,在一些具体的应用中,又可以分出巨型内存分配和微小内存分配,但是使用分治法的思路是没有改变的。
而从对象形式上来看,对象的分配又可分为定长度内存分配和不定长内存分配。这个在前面分配过,定长内存最适合的就是内存池;同样,一些变长内存分配,可以假设成逼近某种定长内存,这些定长内存可以形成一个队列或者类似的链表都可以。而在Go语言的内存分配中可以看到使用的是bitmap。目的当然很清楚,多快好省的建设社会主义。
同样,内存的逻辑管理分配分析OK了,就要说具体的应对措施是什么了。tcmalloc中也是使用span和具体的Page映射,形成一种数据结构,也就是PageHeap。span这个概念在go中分析过,不明白的回去翻翻,基本一样。有了底层的数据结构的支持,就可以考虑分配时如何管理这些Span。也就是对span的分裂和合并并把合适大小的内存分配给需要的对象。
而这个担子就交给了CentralCache,这个也熟悉吧,和Go其中的长得有点像,功能也很像。它通过链表把各种Span链接起来,在需要分配时,把span分配出去,如果没有了span就从PageHeap中申请新的。
但是在并发的情况下,如果仍然使用CentralCache,就无法保证效率或者说竞态会很激烈,这时就可以用 ThreadCache在内存上面再管理一层。这其实就类似于线程的局部存储。每一个线程有自己一个ThreadCache,维护着各种类别的Span的链表,它负责Span的分配,假如耗尽,就像全局的CentralCache申请一批,如果无法申请就去PageHeap申请,再无法申请就去OS,再无法申请就直接挂掉(实际可能不是这样,这里只是说的简单一些)。
在tcmlloc的内存分配过程中向系统申请内存分配,基本使用提mmap和sbkr来实现的。在malloc中128K是一个分界线,但tcmalloc没有查到这个具体的值,回头看源码吧。不过需要注意的是如果sbrk分配失败会继续调用mmap进行分配。

三、整体构成

通过上面的分析可以看出tcmalloc的内存管理结构的整体构成为成三层:
1、ThreadCache
其本身并不复杂就是一系列的以object为基础单元的FreeList链表管理,有就分配出去。重要的是它是线程私有的,效率会很高,不用担心锁的管理。

2、CentralCache
做为全局内存管理的部分,它仍然是管理以object为单元的FreeList,不过它做为内存管理部分,是通过span这个基本单元来控制object的。即CentralFreeList维护着一个个的span的链表,在每个span内又有切分的object的链表。这样就可以实现span的切合和合并。为了实现快速的内存管理,在其中还有一个Cache,用来暂存object。只有当Cache满时才会把相关的object挂到原来的相关span中去。
为了提高效率,FreeList分为两类,即空的和非空的,这样就不用进行判断就可以自动适配相关的条件。

3、PageHeap
此数据结构实现了page到span的映射关系以及空闲span的伙伴系统。看到伙伴系统,学过Linux系统的同学应该就明白怎么回事。这种映射关系的处理是通过radix tree这种数据结构来实现的,它是一种稀疏的叉树,和HASH比,空间更小,并且避免了冲突。在这里,可以通过PageHeapAllocator来处理和分配span、ThreadCache等。
这里面object的处理很有代表性,它没有一个专门的管理数据结构,在使用了,会全部提供给使用方;但在空闲时,会有一个8字节的长度用来存储指针,用来链到相关链表中去。
在tcmalloc的分配过程中,内存管理的基本单元是span,而在上层应用分配的基本单元是object。tcmalloc的分配顺序是从私有到公有到OS内存交互(PageHeap),搞清楚了这个顺序和基本的控制单元,那么在后面的源码分析中就会清楚操作的手段。

四、分配流程

在tcmalloc中将内存分成三类,即小对象,小于256K的,中型对象,介于256K到1M的,大于1M的为大对象。其具体的分配流程如下:
1、小对象内存
通过size映射到指定的class,先在ThreadCache的FreeList中分配,如果成功直接返回即可;否则,去CentralCache分配batch_size个object,其中一个返回,其它直接进入到ThreadCache相关的FreeList(ThreadCache.list_[class])中。在这个过程中会在前面的提到的空闲链表CentralFreeList.tc_slots_[]里面分配;否则去CentralFreeList.nonempty中分配,如果仍然无法分配成功,则去PageHeap中申请一个span,对应的class包含多个page并将其划分成N个object并返回。

2、中型对象内存
PageHeap先从伙伴系统对应npages的span链表里面查找空闲的span(即k个page到128个page,优先查normal链、然后returned链),找到后将其拆分成两个span,一个为K个Page,另外一个为n-k个Page,前者返回,后者放回原来等于n-k的span链表。

3、大对象内存
直接向PageHeap去申请一个刚好大于等于请求size的span。申请过程与中型对象内存分配一致,只是在失败后,会通过sbrk或mmap向系统内存申请并转换成span链表,再重新走上面的分配过程。
这里面有一个class的概念,其实就是object的一些预定义的大小,也就是说变长类型内存划分成了不同的等长内存处理。

五、回收流程

1、释放指针,通过PageHeap的映射关系,返回到Cache,class为0表示是大对象。
2、小对象内存
将内存对象返回到ThreadCache.list_[class],如果其超过指定值(FreeList.length_>=FreeList.max_length_)或者ThreadCache容量越限(ThreadCache.size_>=ThreadCache.max_size_),则启动回收,object被返回到CentralCache的class对应的CentralFreeList上,和分配相反,先尝试batch_size整块回收,并放到相应的Cache的tc_slots_中。
如果Cache已满或者无法满足整块回收,则将单个object回收到sapn.object链表中。通过radix tree如果发现前后的span均为空闲且在normal链则进行合并;而PageHeap会将多余的span回收到returned链上,并会择机进行合并。
3、中对象内存
中型对象的回收和小对象回收一致。
4、大对象内存
由于其指针就是一个span,所以直接丢回PageHeap即可。
这里需要说明一下,returned的span链表表示PageHeap已经将内存返回会系统而normal的span表示应用程序释放后的内存自己控制下。二者虽然空闲且无连续但不同的span块不可以合并。

六、总结

熟悉使用一种机制后,就会越来越发现其相着的优缺点。在实际应用中,就会有意识的扬长避短,但对于一些基础的应用,有时候无可替代,就只能将就一下。但有些人不原意将就,就自己造一个更适合的基础应用的轮子。在国内,缺少的恰恰就是这种基础类的轮子的制造。所谓大的互联网公司,只不过是在别人的应用的基础上再重复造个轮子,吓唬一下没懂行的罢了。
知已知彼,百战不殆。有问题不怕,怕的是不正视问题。继续努力吧。

相关文章:

  • 中国按摩器行业市场需求与投资规划分析报告
  • 分布式医疗大数据存储方案研究综述
  • BOPPPS+课程思政教学模式在计算机导论课程中的应用
  • 中国冶金工程行业数据专项调研分析报告
  • mac (M系列)docker 中elasticsearch 搭建和基础使用 7.15.5版本
  • 党务管理信息系统,让组织人员架构管理更便利,操作更流畅
  • 2022年全球及中国公司秘书服务行业头部企业市场占有率及排名调研报告
  • HTML相关(四)
  • opencv parallel_for_使用及注意
  • 拿下国产高端市场第一背后,vivo与苹果、华为的共性
  • postgresql 实现变量替换框架
  • numpy在数字图像处理中的应用
  • A tour of gRPC:09 - gRPC Interceptor 拦截器
  • 【Docker】——Network
  • Vue3如何实现全屏模式
  • 【腾讯Bugly干货分享】从0到1打造直播 App
  • CAP 一致性协议及应用解析
  • flutter的key在widget list的作用以及必要性
  • Linux编程学习笔记 | Linux IO学习[1] - 文件IO
  • Mysql优化
  • RxJS: 简单入门
  • seaborn 安装成功 + ImportError: DLL load failed: 找不到指定的模块 问题解决
  • Spring框架之我见(三)——IOC、AOP
  • 记录一下第一次使用npm
  • 排序(1):冒泡排序
  • 深度学习在携程攻略社区的应用
  • 使用agvtool更改app version/build
  • 小程序开发中的那些坑
  • 验证码识别技术——15分钟带你突破各种复杂不定长验证码
  • 一个6年java程序员的工作感悟,写给还在迷茫的你
  • - 转 Ext2.0 form使用实例
  • 不要一棍子打翻所有黑盒模型,其实可以让它们发挥作用 ...
  • ​学习一下,什么是预包装食品?​
  • #1014 : Trie树
  • (16)Reactor的测试——响应式Spring的道法术器
  • (33)STM32——485实验笔记
  • (day 12)JavaScript学习笔记(数组3)
  • (Mac上)使用Python进行matplotlib 画图时,中文显示不出来
  • (附源码)springboot美食分享系统 毕业设计 612231
  • (附源码)计算机毕业设计SSM教师教学质量评价系统
  • (更新)A股上市公司华证ESG评级得分稳健性校验ESG得分年均值中位数(2009-2023年.12)
  • (九)One-Wire总线-DS18B20
  • (十一)JAVA springboot ssm b2b2c多用户商城系统源码:服务网关Zuul高级篇
  • (四)【Jmeter】 JMeter的界面布局与组件概述
  • (转)ORM
  • (转)socket Aio demo
  • * CIL library *(* CIL module *) : error LNK2005: _DllMain@12 already defined in mfcs120u.lib(dllmodu
  • ****** 二十三 ******、软设笔记【数据库】-数据操作-常用关系操作、关系运算
  • *ST京蓝入股力合节能 着力绿色智慧城市服务
  • .bat批处理(六):替换字符串中匹配的子串
  • .NET 6 在已知拓扑路径的情况下使用 Dijkstra,A*算法搜索最短路径
  • .net framework profiles /.net framework 配置
  • .Net程序猿乐Android发展---(10)框架布局FrameLayout
  • .NET教程 - 字符串 编码 正则表达式(String Encoding Regular Express)
  • .NET设计模式(7):创建型模式专题总结(Creational Pattern)