前篇《探秘c++内存管理(理论篇)》主要介绍了linux c++程序内存管理的理论基础,本文作为系列文章《探秘c++内存管理》的第二篇,将会探讨经典内存管理器ptmalloc如何管理c++程序的内存。借助剖析ptmalloc解决问题的着重点和设计实现成本的权衡,更具体的呈现c++内存管理面临的问题和工程落地中的巧思。
一、概述
geek talk
ptmalloc是开源gnu c library(glibc)默认的内存管理器,当前大部分linux服务端程序使用的是ptmalloc提供的malloc/free系列函数,而它在性能上远差于meta的jemalloc和google的tcmalloc。服务端程序调用ptmalloc提供的malloc/free函数申请和释放内存,ptmalloc提供对内存的集中管理,以尽可能达到:
用户申请和释放内存更加高效,避免多线程申请内存并发和加锁
寻求与操作系统交互过程中内存占用和malloc/free性能消耗的平衡点,降低内存碎片化,不频繁调用系统调用函数
简单概括ptmalloc的内存管理策略:
预先向操作系统申请并持有一块内存供用户malloc,同时管理已使用和空闲的内存
用户执行free,会将回收的内存管理起来,并执行管理策略决定是否交还给操作系统
接下来,将从ptmalloc数据结构、内存分配及优缺点介绍最经典的c++内存管理器的实现和使用(以32位机为例)。
二、内存管理
geek talk
2.1 数据结构
为了解决多线程锁争夺问题,将内存分配区分为主分配区(main_area)和非主分配区(no_main_area)。同时,为了便于管理内存,对预申请的内存采用边界标记法划分成很多块(chunk);ptmalloc内存分配器中,malloc_chunk是基本组织单元,用于管理不同类型的chunk,功能和大小相近的chunk串联成链表,被称为一个bin。
main_arena与non_main_arena
主分配区和非主分配区形成一个环形链表进行管理, 每一个分配区利用互斥锁实现线程对该分配区的访问互斥。每个进程只有一个主分配区,但允许有多个非主分配区,且非主分配区的数量只增加不减少。主分配区可以访问进程的heap区域和mmap映射区域,即主分配区可以使用sbrk()和mmap()分配内存;非主分配区只能使用mmap()分配内存。
对于不同arena的管理策略大致如下:
分配内存
查看该线程的私有变量中是否已经存在一个分配区并对其进行加锁操作,如果加锁成功,则使用该分配区分配内存;如果未找到该分区或加锁失败,遍历环形链表中获取一个未加锁的分配区
如果整个环形链表中没有未加锁的分配区,开辟一个新的分配区,将其加入循环链表并加锁,使用该分配区满足当前线程的内存分配
释放内存
先获取待释放内存块所在的分配区的锁,如果有其他线程正在使用该分配区,等待其他线程释放该分配区互斥锁后,再释放内存
主分配区和非主分配区的结构如下:
其中fastbinsy和bins是对实际内存块的管理和操作结构:
fastbinsy: 用以保存fast bins
bins[nbins * 2 - 2]: unsorted bin(1个,bin[1])、small bins(62 个,bin[2]~bin[63])、large bins(63 个,bin[64]~bin[126])的集合,一共有 126 个表项(nbins = 128),bin[0] 和 bin[127] 没有被使用
malloc_chunk与bins
ptmalloc统一管理heap和mmap映射区域中空闲的chunk,当用户进行分配请求时,会先试图在空闲的chunk中查找和分割,从而避免频繁的系统调用,降低内存分配的开销。为了更好的管理和查找空闲chunk,在预分配的空间的前后添加了必要的控制信息,内存管理结构malloc_chunk的成员及作用如下:
mchunk_prev_size: 前一个空闲chunk的大小
mchunk_size: 当前chunk的大小
必要的属性标志位:
前一个chunk在使用中(p = 1)
当前chunk是mmap映射区域分配(m = 1)或是heap区域分配(m = 0)
当前chunk属于非主分配区(a = 0)或非主分配区(a = 1)
fd和bk: chunk块空闲时存在,用于将空闲chunk块加入到空闲chunk块链表中统一管理
基于chunk的大小和使用方法,划分出以下几种bins:
fast bins
fast bins仅保存很小的堆,采用单链表串联,增删chunk都发生在链表的头部,进一步提高小内存的分配效率。fast bins记录着大小以8字节递增的bin链表,一般不会和其他堆块合并。
unsorted bin
small bins和large bins的缓冲区,用于加快分配的速度,chunk大小无尺寸限制,用户释放的堆块,会先进入unsorted bin。分配堆块时,会优先检查unsorted bin链表中是否存在合适的堆块,并进行切割并返回。
small bins
保存大小 = 512b的chunk的bin被称为large bins。large bins中的每一个bin分别包含了一个给定范围内的chunk,其中的chunk按大小降序,相同大小按时间降序。
当然,并不是所有chunk都按上述的方式来组织,其他常用的chunk,如:
top chunk: 分配区的顶部空闲内存,当bins不能满足内存分配要求的时候,会尝试在top chunk分配。
当top chunk > 用户请求大小,top chunk会分为两个部分:用户请求大小(user chunk)和剩余top chunk大小(remainder chunk)
当top chunk < 用户所请求大小,top chunk就通过sbrk(main_arena)或mmap(non_main_arena)系统调用来扩容
2.2 内存分配与释放
概括内存malloc和free的流程大致如下:
内存分配malloc流程
1、获取分配区的锁
2、计算出需要分配的内存的chunk实际大小
3、如果chunk的大小 < max_fast,在fast bins上查找适合的chunk;如果不存在,转到5
4、如果chunk大小 max_fast,放入unsorted bin,并且检查是否有合并:
a.没有合并情况则free
b.有合并情况并且和top chunk相邻,转到8
6、如果chunk的大小 64kb,会触发进行fast bins的合并操作,fast bins中的chunk将被遍历合并,合并后的chunk会被放到unsorted bin中。合并后的chunk和top chunk相邻,则会合并到top chunk中,转到8
8.如果top chunk的大小 > mmap收缩阈值(默认为128kb),对于主分配区,会试图归还top chunk中的一部分给操作系统
三、优缺点
geek talk
ptmalloc作为glibc默认的内存管理器,已经广泛的满足大多数大型项目的内存管理,同时它的实现思路也对后来的内存管理器提供了借鉴。
ptmalloc的介绍暂告一段落,接下来的几篇文章将继续探讨高性能内存管理库的集大成者——jemalloc、tcmalloc内存管理库。
手机芯片排名前十名榜单
苹果 iPhone 12 Pro/Pro Max 仍然严重供不应求,最难买到
热敏电阻可分为哪几类
应对多射频硬件设计挑战的方法
电竞显示器怎样才算是“正宗”?从这几个参数一眼便知!
百度工程师带你探秘C++内存管理
Rambus推出支持HBM3的内存子系统,速率可达8.4Gbps,助力AI/ML性能提升
日本政府要求脸书加强用户个人信息安全性
集成电路数据选择器
ups电池更换步骤
蓄电池正极接线端出现虚接会产生哪些弊端
“城市大脑”这样的智慧城市系统,正在逐渐成为大城市的“标配”
Nubia展出一款全新骁龙855小屏旗舰 机身尺寸与小米6相近支持5G
如何通过VOICE实现家庭自动化
汽车蓄电池低电量报警系统设计
AMD锐龙9 3990X处理器无人能挡 未来一两年内都不太可能有同级别的对手出现
[图文]扩大三端稳压块的输入电压范围
交流弧焊机的保养内容
rtu水文专用(支持SL/T427-2021等大部分协议)
OpenCV的起源和应用领域