glibc,ptmalloc,tcmalloc与jemalloc

大部分开源的缓存,数据库,中间件都不会使用glibc的ptmalloc作为内存分配库,而选择tcmalloc和jemalloc,三款库内部都维护了内存池但实现有较大差异。

glibc

glibc是和操作系统api交互的中间层,主要支持了下列功能和封装:
1.通用算法,数学库
2.原子操作库
3.日期时间
4.错误处理
5.输入输出
6.本地环境,系统配置
7.内存管理
8.字符串
9.线程库,进程管理,任务控制
10.文件系统,系统日志
11.用户和组,系统管理,资源管理
12.调试支持
13.sockets编程,管道,fifo,信号,进程间通信

这里主要研究内存管理模块ptmalloc

glibc封装的系统调用有这几种形式:
1.c文件自定义封装实现最后调用SYSCALL_CANCEL或者INLINE_SYSCALL_CALL等宏执行汇编语句中断跳转,一般使用weak_alias弱符号别名,如果其他地方实现了会将它替换。
2.头文件声明,定义通过make-syscalls.sh在构建glibc工程时查找syscalls.list系统调用列表生成,配合syscall-template.S直接汇编中断跳转系统调用。
3.直接使用汇编实现,比如vfork.S这种。

ptmalloc(pthreads malloc)

代码区,数据区(全局(静态,手动初始化的全局区,未初始化自动清0的bss区,不占用内存,程序运行后动态分配的内存都是brk,sbrk分配的bss区内存),动态分配堆区,常量只读区),栈区。

原理

Arena:一个包含多块内存区的数据结构被多线程共享,每个线程想要分配内存要从这个内存区free lists中获取。

Heap:每个堆属于一块完整的Arena,一块连续的内存区被细分成多块chunk块。

chunk:能够被进程分配的小块内存,释放,或组成更大的连续范围的chunk,每个chunk在heap中并且属于一个arena。chunk可以各种大小的链表。

memory:可以是被进程使用的ram物理内存页可以是被swap到磁盘的空间。

1.主arena使用应用程序的堆,其他arena使用mmap分配的堆。chunk可以使用主arena,也可以使用mmap的arena。

2.mmap分配的chunk,不是堆的一部分。

3.可以设置chunk的标志位为prev in use,这样相当于锁住了chunk对应的页,不会被换出。

4.chunk有一个heap_info指针,可以获取chunk所在堆的信息。

5.小块chunk被加入到fastbin中,分配小块内存直接到这里获取。被释放的chunk会放在一起然后排序,相邻的块会合并更大的块。如果想获取小块chunk,但只有大块chunk可能会将chunk一分为二使用。

6.多线程情况下,每个线程会记录自己用过的那块arena,即使业务逻辑访问的是不同内存,但底层仍然会存在多线程竞争一块内存的情况,内部就需要锁。当然线程也有自己的本地内存tcache,也是用链表连接的多块chunk,但是内存比较小。注意tcache和threadlocal不一样(tcache指针是存在__thread线程本地变量中),tcache指向的内存仍然使用mallco分配,应用层不知道是怎么分配的,只是一种优化机制,在tcache有满足条件的chunk时会优先使用。

7.优先分配顺序是tcache,mmap fastbin smallbin topchunk中其他合适chunk或者合并chunk或者拆分chunk。

8.主arena其实就是进程初始化分配的堆,可以使用brk,sbrk分配空间,不够了会自动生长。

9.优先回收顺序是tcache,unmap fastbin 未排序列表,其他块合并,chunk足够大了考虑还给操作系统。

10.mmap出来的arean内存不够用了,使用mremap整体迁移;

自定义自己的malloc

可以修改库的依赖地址(静态库要把自己实现的放在前面链接),或者使用LD_PRELOAD加上自己动态库的地址。glibc还带有obstacks内存池,开源项目很少看到有使用的。malloc,free等函数的替换使用的技巧是弱引用__weak,编译器默认强引用可以替换弱引用,且不加弱引用前缀默认是强引用。

其他

  1. mmap使用mprotect来设置内存地址权限。
  2. 锁内存页,如果开启了swap,不想让某些内存页面被换出去,就可以使用锁页机制mlock,mlock2。

tcmalloc

和gperftools的关系

perftools是分析工具,其中带有tcmalloc的代码但是tcmalloc版本比较老了没有更新成最新的;而tcmalloc的独立版本一直有更新优化,使用bazel编译。

原理

在这里插入图片描述
1.架构分成前端中端后端,前端复制快速分配内存,中端负责前端和后端内存的分配释放,后端负责和操作系统的交互。

2.前后端要获取或者返回内存同一时间只会被一个线程处理,需要mutex lock保护。

3.中端 使用CentralFreeList 和 TransferCache来保管内存。

4.中端内存不满足,向后端获取。

5.有两种分配模式,per-thread和per-cpu模式,per-cpu会更好,因为cache可以更少失效。

6.后端有两种分配方式,一种按页单位大小的chunk块legacy pageheap(最多255页),另一种是大内存hugepage(需要内核开启大页配置,一般大小是2mb),使用大内存可以减少tlb miss的机会。

per-cpu

1.前端内存可通过MallocExtension::SetMaxPerCpuCacheSize限制大小。

2.释放的线程会挂到MallocExtension::ReleaseCpuMemory上。

per-thread

1.每个线程有独立的thread-local缓存,并且分成不同的大小的链表。

2.前端最大大小同样有一个参数MallocExtension::SetMaxTotalThreadCacheBytes限制。还有一个最小限制KMinThreadCacheSize,默认512k。

transfer cache和central free list

1.先使用transfer cache,不满足再使用central free list。

2.central free list使用spans管理。和后端交互的其实是central free list以span为单位。

3.span最小单位是页,挂在pagemap上,pagemap分两到三层,数据结构是radix-tree。

4.每个span可以拆分最多2的16次方的小对象,每个小对象的索引占两个字节。

jemalloc

jemalloc除了标准malloc一系列api,还有自己的非标准的api。可通过 --with-malloc-conf,/etc/malloc.conf,或者环境变量MALLOC_CONF来配置属性。

原理

1.和tcmalloc的thread-cache有点类似,以多线程下少竞争少cache miss为目标,但是并不是前中后端的架构。当然也可以为线程配置per-cpu选项。

2.小对象放到slabs中管理,每个slab通过bitmap跟踪分配情况。

3.配置
background_thread:开启后台线程,可以降低分配延迟,建议开启。
metadata_thp:开启透明大页,默认开启。
dirty_decay_ms和muzzy_decay_ms:内存回收时间配置。
narenas:arena的数量,多了会造成更多碎片,好处是锁定更少,速度快;少了能提高内存使用率,速度慢一些。
percpu_arena:内存在线程间迁移不频繁的话建议开启。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值