Ptmalloc
内存分配
- Ptmalloc有多个area,包括一个在堆上分配内存的主分配区和使用mmap分配内存的非主分配区,使用area分配内存、释放内存、合并相邻空闲chunk都需要加锁。Ptmalloc分配内存时尝试获取一个可以加上锁的area,如果获取不到并且area没有超过上限会创建一个新的area;当达到area上限且加锁失败会循环尝试加锁直到获取到一个area。
- 用户请求分配的内存Ptmalloc中使用chunk表示, 每个chunk至少需要8个字节额外的开销。 Ptmalloc 将相似大小的chunk 用双向链表链接起来, 这样的一个链表被称为一个 bin。Ptmalloc 一共 维护了128 个bin,并使用一个数组来存储这些 bin。
- Ptmalloc会将分配的内存大小按16字节向上对齐,在内存池中找到相似大小的chunk并直接使用,如果找不到则找更大的chunk,然后拆为两部分,一部分分配给用户,一部分重新链接到合适的chunk下。
- 如果内存池中获取失败则会向系统申请。
内存优化总结:ptmalloc、tcmalloc和jemalloc
内存释放
- Ptmalloc中,所有调用delete释放的内存,并不是立即调用brk(sbrk)归还给操作系统,而是先将这个内存块挂在free-list里面进行内存归并(相邻的可用内存块合并为更大的可用内存块)。如果还与top chunk相邻则会合并到top chunk,当top chunk的大小达到malloc_trim的threshhold就会则调用malloc_trim归还部分可用内存给操作系统。
- glibc中设置了默认进行malloc_trim的threshhold为128K,也就是说当ptmalloc管理的free-list顶部最大连续可用内存>128K时,就会执行malloc_trim操作,归还部分内存给操作系统;而在可用内存<=128K时,即使程序中delete了这部分内存,这些内存也是不会归还给操作系统的。对外表现为调用delete之后,进程占用的内存并没有减少。
- 可调用malloc_trim(0)主动释放内存,它有两个作用:将threshhold设为0,释放free-list最顶层的内存;解除内存空洞中虚拟地址到物理地址的映射,把空洞的物理内存返回给系统,
- 调用mmap申请的内存会直接释放。
Ptmalloc的缺陷
- 后分配的内存先释放, ptmalloc 收缩内存是从free-list的 top chunk 开始,如果与 top chunk 相邻的 chunk不能释放, top chunk 以下的 chunk 都无法释放造成大量内存空洞。默认情况下top chunk的连续空闲内存大于128K时才会主动释放归还给操作系统。
- 多线程分配和释放内存以及合并空闲chunk时,需要对分配区area加锁造成比较大的开销。
- 内存从areana中分配, 不能从一个arena移动到另一个arena。如果多线程使用内存不均衡容易导致内存的浪费。 比如线程1使用了300M内存,完成任务后glibc没有释放给操作系统,线程2开始创建了一个新的arena,但是线程1的300M却不能用了。
- 用户请求分配的内存在ptmalloc中使用chunk表示, 每个chunk至少需要8个字节额外的开销。
Tcmalloc
TCMalloc解密
TCMalloc : Thread-Caching Malloc
相关概念
Size Class
Tcmalloc按大小分配了88个Size Class,每个Size Class都代表了一个大小,比如8B,16B,32B…256KB,16个字节以下间隔8字节。程序申请内存时会向上取整到Size Class的大小,比如7B申请8B的空间,15B申请16B的空间。
Span
Tcmalloc将虚拟内存分割成若干个8KB大小的page,连续n个page称为1个span。
ThreadCache
Tcmalloc为每个线程分配了一份单独的ThreadCache,每个ThreadCache对于每种Size Class都维持了一个链表,缓存了若干该Size Class大小的空闲对象。因为是thread local,所以从ThreadCache分配内存和释放内存不需要加锁,速度比较快。
CentralCache
ThreaCache的空间是从CentralCache申请来的,CentralCache是多个线程共享的一个缓存。它也为每种Size Class维持了一个链表,供各个线程的ThreadCache从此处申请和归还内存。因为需要加锁,所以ThreadCache每次会申请或者回收多个空闲空间。
PageHeap
CentralCache的空闲空间是从PageHeap申请的。PageHeap根据span的大小采取了2种不同的缓存策略,128page内的span,每个大小都用一个链表来缓存。超过128page大小的span存储在set中。
小对象分配
0-256KB的对象称为小对象,分配小内存对象主要有以下几步:
- 向上对齐到对应Size Class大小并遍历该Size Class大小的,然后遍历该链表找到第一个对象,将其从链表删除并返回;
- 如果ThreadCache对应的链表为空,则向CentralCache一次性申请若干个Size Class大小的空闲对象放到自己的链表上,重复1操作;
- 如果CentralCache也没有空闲空间,则向PageHeap申请1个best fit的span并拆分成若干个Size Class大小的对象放到CentralCache对应的链表上,CentralCache再把这些对象中的一部分放回到ThreadCache的链表上,重复1操作
中对象分配
256KB-1M的对象称为中对象,分配中对象主要有以下几步:
- 向上对齐到k个page,从k个page的span链表到128个page的span链表为止,找到第一个非空的span链表(n个page),取出第一个span拆为两部分:第一部分为k个page的span直接返回;剩下的n-k个page插入到n-k个page的span链表中;
- 如果128个page的span链表页无法满足要求,则视为大对象分配
大对象分配
大于1M的对象视为大对象,分配大对象主要有以下几步:
- 向上对齐到k个page,从存储span的set中找到best fit的span,拆分为两部分:第一部分为k个page的span直接返回;剩下的n-k个page组成的span如果大于128page,则插入到set中,否则插入到n-k个page的span链表中
- 如果set中也找不到合适的span,则调用brk或mmap向系统申请新的内存生成新的span,重新执行对应的对象分配算法
综上,三种对象的内存分配可以通过下图直观理解,不超过256KB的小对象分配,在应用程序和内存之间其实有三层缓存:PageHeap、CentralCache、ThreadCache。而中对象和大对象分配,则只有PageHeap一层缓存。
内存释放
当一个对象被回收时,Tcmalloc会计算它的大小。
-
小对象会被插入对应ThreadCache的SizeClass链表中;如果ThreadCache超过了预定大小,会将ThreadCache中的一部分空闲对象移到CentralCache对应SizeClass的链表中;当满足一定条件时,CentralCache中的空闲对象也会还给PageHeap,PageHeap再还给系统。
-
非小对象则直接放入PageHeap中best fit的链表或者set中。
Tcmalloc的优点
- ThreadCache会阶段性的回收内存到CentralCache里。 解决了ptmalloc2中arena之间不能迁移的问题。
- Tcmalloc占用更少的额外空间。例如,分配N个8字节对象可能要使用大约8N *1.01字节的空间。即多用百分之一的空间。Ptmalloc2使用最少8字节描述一个chunk。
- 更快, 256KB以下的小对象内存无锁分配。