关于内存分配器ptmalloc以及tcmalloc的详细讲解:
为什么要有内存管理
因为brk、sbrk、mmap都属于系统调用,若每次申请内存,都调用这三个,那么每次都会产生系统调用,影响性能;其次,这样申请的内存容易产生碎片,因为堆是从低地址到高地址,如果高地址的内存没有被释放,低地址的内存就很难被回收(当然,共享区没有这个限制)。因此内存管理其实有两个作用:
- 优化性能
- 缓解内存碎片问题(一方面是管理并复用brk产生的碎片,一方面解决内存池为了性能本身把内存分块管理造成的碎片问题)
内存管理概述
内存管理分为用户程序层、C 运行时库层、内核层三个层面,其中分配器 allocator 属于 C 运行时库的内存管理模块,它负责响应用户程序的内存分配请求,再向内核申请内存并返回给用户;为提升分配效率,allocator 通常会预先申请一块远超用户需求的内存自行管理,用户释放的内存也不会立即还给操作系统,而是由 allocator 统一维护空闲内存块,后续有新分配需求时会优先从空闲空间中匹配合适内存,匹配不到才再向内核申请新内存,业界主流的内存分配器有 glibc 默认标配的 ptmalloc、Google 的 tcmalloc 以及 Facebook 的 jemalloc。
ptmalloc与tcmalloc对比
- 线程并发处理
- ptmalloc:通过将内存分为多个分配区(arena)来缓解多线程锁竞争,但分配区数量有限,且一个分配区可能被多个线程共享,因此在高并发下仍存在性能瓶颈。
- tcmalloc:为每个线程分配一个 threadCache,实现无锁的内存申请和释放,大幅提升并发性能。
- 内存局部性优化
- ptmalloc:使用 last remainder chunk 来提升局部性,效果不确定。
- tcmalloc:每个线程几乎总是从同一个 Span 切割同大小内存块,自然具备更好的局部性,对连续小块内存分配更高效。
- 内存占用
- ptmalloc:内存占用不是特别多,因为分配区是有限的。但是****长时间运行容易出现内存暴增,因为只有靠近 top chunk 的内存块被释放时才会触发缩容。
- tcmalloc:内存占用很多,因为每个线程都要有一份ThreadCache以及其中的各种类型的内存块
- 小块内存合并
- ptmalloc:合并小块内存时,总是需要向前向后遍历空闲块,操作相对较慢。
- tcmalloc:小块内存合并非常快且自然,只需对应 Span 的计数器减减即可。
- 资源调度与忙闲不均
- ptmalloc:无法合理处理线程使用不均的情况。例如,线程 A 频繁申请内存,线程 B 几乎不申请,B 却浪费了分配区资源。
- tcmalloc:使用 centralCache 进行线程间的合理调度,避免资源浪费。
总结
ptmalloc 适合通用、线程数不极端的环境,无明显偏好,兼容性最好。
tcmalloc 在多线程高并发、频繁分配/释放大量小对象的场景下优势显著(如果频繁),尤其适合对延迟敏感以及内存消耗不敏感的服务。