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

[形象解析] ptmalloc、tcmalloc与jemalloc对比

前言

对于ptmalloc,tcmalloc,jemalloc网上有很多解析,讲的很好也很仔细,但是想要彻底看懂还是很有难度的,我这篇文章主要以自己的理解并结合形象的举例对比三者的区别,相对于大部分文章我不会深入的探讨三者的具体结构,而是从接触新手的角度,通过对比三者的区别,以举例子让大家对这三个常见内存池有一个整体且直观的概念,其中有很多地方舍弃了结构设计的细节,只保留核心的申请释放过程,讲解相对来讲没有那么严谨,如有错误请在评论区指正。

一、内存池

1)池化技术

池化技术是指将一类资源统一管理并重复利用,以减少资源分配和释放的频率,从而提升效率并降低系统开销。在内存管理中,池化技术主要用于小对象的内存分配,通过预先分配一块较大的内存区域,将其划分为多个固定大小的小块以满足频繁的内存请求。池化技术的核心思想是“重用”,尤其在那些内存分配和释放非常频繁的应用中,这种技术能够显著提高性能。

池化技术就像餐馆里的自助餐盘回收机制。想象一个高峰时段的餐馆,如果每次客人用餐后都需要餐馆去购买新的餐盘,显然效率会非常低,还会浪费资源。为了避免这种情况,餐馆会准备一批固定数量的餐盘,当客人用完后,服务员会清洗并重新放回取餐区域供下一个客人使用。这种重复利用餐盘的方式,不仅节省了资源,还让整个用餐流程更加高效。

在内存管理中,池化技术就像这些餐盘:系统预先准备了一批内存块,程序用完后会将它们归还,供后续的内存请求使用,从而避免频繁向操作系统申请和释放内存所带来的开销与混乱。

池化的优点包括:

  • 减少分配开销:减少调用操作系统的内存分配接口(如 mallocnew)的次数。
  • 提升性能:固定大小的内存块分配更快,同时避免碎片化问题。
  • 降低内存碎片:通过统一管理,池化技术更容易控制和优化内存布局。

典型应用场景包括网络编程中的连接池、线程池,以及游戏开发中的对象池等。

2)内存池

内存池是一种基于池化技术实现的内存管理机制,它通过预分配一块内存区域,并对其进行统一管理,为小对象的内存分配提供支持。内存池通常会按照特定规则将内存分为多个固定大小的块(或称为“单元”),并在需要时将这些块分配给用户,释放时再归还到内存池中以供后续使用。

内存池的核心在于复用已分配的内存块,而不是频繁向操作系统请求和释放内存。这样可以显著降低系统调用的开销,同时避免因频繁分配和释放而导致的内存碎片问题。

1. 内存池的基本原理与目标
  • 预分配内存

    • 内存池在初始化时一次性向操作系统申请一大块连续的内存区域。
    • 这块区域被划分为若干个大小一致的内存块,以支持频繁的小对象分配需求。
  • 内存管理策略

    • 空闲链表:内存池会维护一个空闲链表,用于记录所有未被分配的内存块。当有新的分配请求时,从链表中取出一个空闲块并返回;释放时再将其归还到链表中。

    • 分区分配:有些内存池实现会根据块大小将内存池划分为不同的区域,以适配不同大小的对象分配需求。

  • 减少系统调用

    • 通过复用内存块,内存池可以减少对操作系统的内存分配和释放调用(如 mallocfree),降低了系统调用带来的开销。
  • 目标

    • 提升效率:提供更快的内存分配和释放速度。

    • 减少碎片:通过固定大小块的分配,避免内存碎片问题。

    • 优化资源利用率:内存池可以帮助应用程序在高并发场景中高效管理内存,减少内存不足的问题。

2. 内存池设计考虑

为了实现一个高效的内存池,在设计中需要考虑以下关键因素:

  • 分配效率:如何快速找到一个可用的内存块?是否需要支持多线程并发分配?

  • 内存利用率:设计合理的块大小,以避免因分配过多大块内存而浪费资源。

  • 线程安全:是否需要对内存池进行线程同步,或者采用线程本地池来减少锁争用?

  • 可扩展性:如果内存池的预分配区域不够,是否支持动态扩展?如何回收不再使用的内存块?

  • 内存对齐:确保分配的内存满足平台对齐要求,避免因未对齐导致的性能问题或错误。

  • 内存生命周期管理:如何确保分配的内存块在释放时被正确归还?是否需要垃圾回收机制?

  • 定位错误:为了调试方便,内存池应提供检测非法访问或重复释放的机制。

  • 跨平台支持:内存池的实现应尽可能适配多种操作系统和硬件架构。

  • 内存监控:提供内存池的使用统计(如分配次数、空闲块数量)以方便调试和优化。

二、ptmalloc

1)介绍

20 世纪 90 年代,随着多核处理器和多线程编程模型的普及,传统的内存分配器(如 dlmalloc)因其单线程设计,在并发场景中表现不佳,特别是在多线程程序频繁调用 mallocfree 时,容易导致锁争用,进而成为性能瓶颈。

为了应对这一问题,ptmalloc(pthread malloc)应运而生。它在 dlmalloc 的基础上进行了优化,通过引入线程局部内存管理策略,减少了全局锁的竞争,从而提升了多线程环境下的内存分配效率。

具体来讲,ptmalloc 是由 Wolfgang Gloger 在 1997 年开发的基于 dlmalloc(Doug Lea Malloc)的改进版本,专为解决多线程环境中的内存分配问题。随着多线程和多核处理器的普及,传统的单线程内存分配器(如 dlmalloc)在并发场景下效率较低,尤其在频繁调用 mallocfree 时容易发生锁争用,影响性能。ptmalloc 基于 dlmalloc,通过引入多线程支持的 arena 机制,显著优化了并发环境中的内存分配效率。

可以把 ptmalloc 的改进形象地看成一个大型自助餐厅的服务流程优化:

在传统的餐厅(类似于 dlmalloc)中,每次客人(线程)需要用餐时,都必须到服务台排队领取食物(获取内存)(类似于调用 malloc)。如果餐厅很忙,所有客人都集中到一个服务台,就会造成拥堵(即锁争用),影响用餐效率。

而在改进后的餐厅(类似于 ptmalloc)中,餐厅经理(ptmalloc 的设计者)决定把食物分散到不同的区域,每个区域都有一套独立的自助食物领取站(arena)。每位客人按照就近的规则到自主食物领取站获取食物,不需要再排队到服务台,只有在一个领取站食物完全消耗,才会需要经理协调重新补充(对应极少的全局锁操作)。

这种改进的好处是:

  • 每个区域的服务都是独立的(即一个arena有一把锁,如果不向同一个arena索要内存就不会加锁),不会互相干扰。
  • 减少了集中管理带来的排队和等待(全局锁争用),提升了整体效率。
  • 适合高峰期使用(多线程高并发场景)。

通过这种优化,ptmalloc 在多线程环境下的内存分配性能得到了显著提升,就像改进后的餐厅能更高效地服务客人一样。

2)ptmalloc解析

1. 核心概念

了解ptmalloc前我们要明白两个核心概念:

  • Arena(分配区):每个线程会优先使用自己的分配区,减少与其他线程的锁竞争。如果没有可用的分配区,系统会动态创建新的分配区。

  • Chunk(内存块):指内存池中分配的最小单位,每次内存申请都会从Arena分配区中切割出适合大小的内存块返回给用户。对于超大内存申请,直接调用操作系统的 mmap 接口分配内存。

2. 线程与分配器的关系

ptmalloc 通过 动态创建多个 arena 来应对多线程的内存分配需求。

  • 每个 arena 是一个独立的内存分配区,用于减少线程之间的竞争。

  • 当一个线程需要分配内存时,会尝试绑定到一个已有的 arena(如主 arena 或动态创建的 arena)。

  • 如果当前所有 arena 都被线程争用,ptmalloc 会动态创建一个新的 arena,分配给新的线程。

  • 特点:一个线程并不总是有自己的专属分配器,而是从有限数量的 arena 中随机选取或共享。

  • 关键点:

    • 多个线程可能共享同一个 arena(因此可能存在锁竞争)。

    • 动态分配的 arena 是系统级资源,并不固定与线程绑定。

3. 为什么多个线程可能共享一个 arena?

虽然 ptmalloc 会动态创建新的 arena 来减少线程争用,但它并不是“线程 一 arena” 的模型,而是一个 多线程共享有限数量 arena 的模型。原因如下:

  • 初始设计是有限的 arena

    • 主 arena:ptmalloc 初始化时只有一个主 arena,所有线程默认会优先尝试使用这个主 arena。

    • 动态 arena:只有在主 arena 被锁争用得非常严重时,ptmalloc 才会动态分配新的 arena。但即使动态创建了多个 arena,这些 arena 的数量是有限的,并不是按线程数一一对应。

  • 线程绑定到 arena 的机制

    • ptmalloc 使用一种 “线程绑定到可用 arena” 的策略,而不是强制为每个线程创建一个独立的 arena。

    • 当一个线程请求内存分配时,会按照如下步骤查找可用的 arena:

      • 优先尝试获取主 arena。

      • 如果主 arena 被其他线程锁定,尝试绑定到已有的动态 arena。

      • 如果所有 arena 都被锁定,才会创建一个新的 arena。

    • 一旦线程绑定到某个 arena,后续的分配操作会优先使用这个 arena。

  • arena 数量是有限的

    • 动态创建的 arena 是有上限的,这个上限由以下因素决定:

      • 系统支持的并发程度。
      • 可用内存的大小。
      • 系统配置参数 M_ARENA_MAX(默认为 CPU 核心数的两倍)。
    • 当达到这个上限时,不会再创建新的 arena,所有线程只能在已有的 arena 中共享资源。

  • 并非所有线程同时高并发

    • 即使 arena 是共享的,实际运行中,并非所有线程都同时需要分配内存。

    • 在低负载或轻度并发的场景中,多个线程可能因为未触发动态创建逻辑,继续共享同一个 arena。

4. 内存释放

内存释放时,ptmalloc 将释放的内存块归还到所属的Arena分配区,加入空闲链表中等待复用。如果内存块是通过 mmap 直接分配的,则会直接释放回操作系统,避免占用额外资源。

4)优点

  • 高效的多线程支持:通过引入 arena 机制,减少了线程间的锁争用。
  • 动态扩展能力:分配区数量可根据需求动态增加,适应高并发场景。
  • 系统集成:作为 glibc 的默认内存分配器,广泛应用于 Linux 系统,兼容性强。

5)缺点

  • 内存碎片化:由于分区管理,内存碎片问题在特定场景中可能更加明显。
  • 动态分配区增长不可回收:一旦创建新的分配区,分配区数量不会减少,可能导致内存资源浪费。
  • 适应性有限:对超大内存分配或非标准分配场景的支持不如一些专用分配器(如 tcmalloc 或 jemalloc)。

三、tcmalloc

1)介绍

随着多核处理器的发展,传统的内存分配器(如 dlmallocptmalloc)在高并发场景下的性能虽然有所改进,但仍存在一定的局限性。例如:

  • 锁争用问题:即使引入了 arena,多个线程访问同一 arena 时仍可能导致锁竞争。
  • 内存碎片化:频繁的内存分配和释放会导致内存碎片化,降低整体内存利用率。

为了解决这些问题,Google 开发了 tcmalloc(Thread-Caching Malloc),这是一个高性能的多线程内存分配器。它通过引入线程本地缓存机制(Thread Cache)和精细化的内存管理策略,进一步提升了多线程环境下的内存分配效率,并显著减少了内存碎片。

可以将 tcmalloc 的改进比作一个更加智能化的餐厅管理系统:

在传统的餐厅(如 ptmalloc)中,虽然有多个分区(arena)分散了压力,但当分区的客人过多时,仍可能出现拥堵。
而在 tcmalloc 的餐厅中,

  • 每位客人(线程)都有自己的便携小推车(线程本地缓存),上面备有各式的食物(大小不同的内存块)。
  • 客人只需直接从推车上取用,无需等待。如果自己的推车上某种食物(大小不同的内存块)没了就会向对应主管要(这里一个主管只管一种食物)。
  • 如果对应主管也没有食物了则向对应食物的厨师要(一个厨师也只管一种食物的制作)。

这样改进的好处是:

  • 每个客人只要有食物就不会和其他人竞争,小推车和客人一一对应,所以没有竞争。
  • 即便某个客人食物吃光了,向主管要,只要别人没有正好也吃光了该食物,向同一个主管要就不会发生竞争。

2)tcmalloc解析

1. 核心概念
  • 线程本地缓存(Thread Cache) => 客人的食物餐车

    • 每个线程拥有一个独立的缓存,用于管理和快速分配小型内存块(通常小于 256 KB)。
    • 减少了线程间的锁争用,因为绝大多数内存分配和释放操作都发生在本地缓存中。
  • 中央空闲列表(Central FreeList)=> 一个一个管理不同种类食物分配的主管

    • 按大小分类管理内存块,每种大小的内存块由独立的链表维护。
    • 当线程本地缓存的内存不足时,会从中央空闲链表中获取补充。
    • 锁机制:每个链表都有独立的锁,多个线程同时访问不同大小内存块时可以并行操作。
  • 页堆(PageHeap)=> 一个一个制作不同种类食物的厨师

    • 管理更大粒度的内存块(通常以页为单位)。
    • 向操作系统批量申请内存,并根据需要分割成较大内存块,供中央空闲链表或直接分配给用户。
    • 用于处理大于 256 KB 的内存请求,或者在小块内存不足时进行补充。
2. 内存申请
  • 小内存块(<256 KB)
    • 优先从线程本地缓存中获取。
    • 如果线程本地缓存中没有合适的内存块,则向中央空闲链表(Central FreeList)请求。
    • 如果中央空闲链表中也没有足够的内存块,则从页堆(PageHeap)获取内存,并补充到中央空闲链表。
  • 大内存块(≥256 KB)
    • 直接向页堆(PageHeap)申请分配。
    • 页堆会根据需要向操作系统申请内存(通常通过 mmapsbrk),并返回给用户。
3. 内存释放
  • 小内存块(<256 KB)

    • 优先释放回线程本地缓存,以供后续使用。

    • 释放回中心自由链表:

      • 如果线程本地缓存已达到容量上限,则释放到中央空闲链表。
    • 释放回页堆:

      • 如果某种特定大小的内存块在一段时间内频繁释放,而新的分配需求很少,导致某种大小的内存块在中央空闲链表中堆积过多。
      • tcmalloc 的释放策略倾向于批量操作。例如,当某种大小的内存块累积到一定数量(超出设定阈值)时,多余的内存块会从中央空闲链表中释放回页堆,以减少内存占用。
      • 如果多个连续的空闲内存块可以合并为一个较大的内存块,中央空闲链表会尝试合并它们并释放回页堆,从而提升内存利用率。
    • 页堆释放回操作系统:

      • 页堆会追踪每个页的使用状态。如果某些页完全没有被使用(即页内所有内存块都已释放),这些页将被标记为空闲。
      • 如果相邻的页都处于空闲状态,页堆会将这些页合并为一个更大的连续内存块,以便统一释放回操作系统。
      • tcmalloc 定期检查页堆的状态,当发现大量空闲页时,会尝试释放回操作系统。
      • 如果操作系统发出内存紧张的信号,页堆会主动释放空闲页,降低进程的内存占用。
  • 大内存块(≥256 KB)

    • 直接释放回页堆。

    • 页堆可能会将这些内存合并,减少碎片化。

    • 当页堆中的某些内存块完全空闲时,可能会归还给操作系统,降低内存占用。

4)优点

  • 极低的锁竞争:通过线程本地缓存机制,大部分分配操作完全避免了全局锁的争用。

  • 高效的小块分配:线程本地缓存可以快速响应小型内存申请请求,无需频繁访问全局内存池。

  • 碎片化控制:精细的内存管理策略和块合并机制有效减少了内存碎片。

  • 批量操作优化:内存分配和释放以批量为单位,进一步提升了操作效率。

5)缺点

  • 线程本地缓存内存浪费:线程本地缓存可能导致部分内存长期未被使用,但也无法被其他线程复用。

  • 较大的内存占用:相比于传统的分配器,tcmalloc 为了减少碎片化和提升效率,通常会占用更多的内存。

  • 依赖特定场景:在单线程或低并发场景下,其优化效果不明显,甚至可能带来额外的性能开销。

四、jemalloc

1)介绍

jemalloc 是由 Jason Evans 于 2005 年开发的高性能内存分配器,最初为 FreeBSD 设计,现已成为 Firefox、Redis、Netty 等高性能系统的核心组件。其核心目标是解决多核时代下的内存碎片问题和并发扩展性瓶颈,通过分区式内存管理精细化内存分类实现高效分配。与 ptmalloc 和 tcmalloc 相比,jemalloc 在碎片控制和多线程性能上表现尤为突出。

jemalloc在设计上和tcmalloc有不少相似,可以将 jemalloc 的设计改进比作连锁餐厅:

  • 不同的食客均衡的分配到不同餐厅
  • 不同餐厅里每位食客任然有自己的推车。
  • 只有同一个餐厅里的不同客人的同一种食物用完才会产生竞争
  • 对于食物的分配更节约,如果食物长时间不食用,对应食物的主管回激进的拿走

这样改进的好处是:

  • 每个类别的服务独立且高效(避免跨线程争用)。
  • 线程之间的竞争会更小
  • 对内存的利用率更高
  • 除此之外tcmalloc对大内存的申请管理比较粗糙,jemalloc则比较细致。

2)jemalloc解析

1. 核心概念
  • TCache(Thread Cache)

    • 线程本地缓存:
      • 每个线程都有独立的 TCache,用于缓存常用的小内存块(通常小于 32KB)。
      • TCache 的核心优势是线程独占,分配和回收操作完全无锁,极大提升了小内存块分配的性能。
    • 工作原理:
      1. 优先分配: 如果线程需要小内存块(如 16B、32B),优先从自己的 TCache 获取。
      2. 缓存补充: 如果 TCache 中的某种大小内存块耗尽,则会向线程绑定的 Arena 的 Bin 请求。
      3. 缓存回收: 线程释放小内存时,优先归还到自己的 TCache,而不是直接返回给 Arena,避免频繁的跨线程锁竞争。
  • Arena

    • 多分区设计:
      • jemalloc 将内存池划分为多个 Arena,每个线程默认绑定到一个固定的 Arena。
      • Arena 是独立的分区管理单元,包含多个按大小分类的内存分配池(Bin)和大块内存管理模块(Extent)。
    • 作用:
      • 分散线程的内存分配请求,避免所有线程争抢一个全局结构。
      • 每个 Arena 内部独立管理内存,分配和回收操作互不干扰。
    • 绑定策略:
      • 默认情况下,jemalloc 会根据线程的创建顺序,轮询分配线程到不同的 Arena。
      • 如果 Arena 数量小于线程数,则多个线程可能绑定到同一个 Arena,竞争的概率会略微增加。
  • Bin(大小分类缓存)

    • 内部分区:
      • 每个 Arena 内部根据内存块大小,进一步划分为多个 Bin,每个 Bin 专门管理某一固定大小范围的内存块。
      • 小内存块(如 8B、16B、32B)由 Bin 管理,Bin 中的内存块通过链表或 bitmap 组织。
    • 并发控制:
      • 每个 Bin 独立加锁,但由于线程只会访问绑定的 Arena 中的 Bin,锁竞争的概率大大降低。
    • 补充机制:
      • 当线程的 TCache 中某种大小内存块用完时,Arena 会从对应的 Bin 中批量提供新的内存块,供线程使用。
  • Extent(大块内存管理)
    • 定义:
      • Extent 是用于管理大块内存(通常超过 32KB)的模块,不依赖 Bin。
      • 它直接从操作系统(通过 mmapsbrk)获取内存,并进行切割或合并以满足分配需求。
    • 分配逻辑:
      1. 当线程需要大块内存时(如 1MB),Arena 会直接调用 Extent 管理模块进行分配。
      2. 分配的大块内存通常是页对齐的,以便更高效地映射到物理内存。
    • 回收与碎片管理:
      • 回收的 Extent 会被标记为空闲,等待重新分配。
      • 如果多个相邻的 Extent 都空闲,Arena 会将它们合并,减少外部碎片。
2. 内存申请
  • 小内存块(<56 KB)
    • TCache 优先分配
      • 如果线程启用了 TCache,Small Object 优先从 TCache 获取。
      • TCache 是线程本地缓存,存储预分配好的小内存块,避免跨线程锁竞争。
    • 从 Arena 的 Bin 获取
      • 当 TCache 中没有可用的内存块时,线程会向绑定的 Arena 请求分配。
      • Arena 内部将 Small Object 划分到不同的 Bin(按内存块大小分区,如 8B、16B、32B 等)。
      • 每个 Bin 独立加锁管理,分配时仅需操作对应大小的链表或 bitmap,无需全局锁。
    • 内存块补充
      • 当某个 Bin 的内存块耗尽,Arena 会调用底层的 Extent 模块批量分配更大的内存页(通常为 4KB 或更大),并切割成对应大小的内存块存入 Bin。
    • 特点
      • TCache 的无锁分配显著提高了小内存的分配效率。
      • Bin 的独立加锁设计使得小内存块的分配和释放在高并发下性能更优。
  • 中内存块(56KB-4MB)
    • 直接由 Arena 管理
      • Large Object 的分配绕过了 Bin,不会进入 TCache。
      • Arena 直接调用底层的 Extent 模块分配大块内存(通常为页对齐的块)。
    • 内存对齐与管理
      • 分配的大块内存页按请求大小对齐,例如 64KB 或更大的对齐粒度。
      • 分配后的内存块会记录到 Arena 的管理表中,方便释放时快速查找并回收。
    • 内存回收
      • 当释放 Large Object 时,Arena 会将该内存块标记为空闲,并尝试合并相邻的空闲块。
    • 特点
      • 分配过程需要页对齐,效率略低于 Small Object 的分配。
      • 更注重减少外部碎片和高效回收。
  • 大内存块(>4MB)
    • 直接调用操作系统接口
      • Huge Object 的分配完全绕过 Arena,由 jemalloc 调用操作系统的 mmapsbrk 直接分配物理内存页。
      • 每次分配都独立完成,无需切割或缓存。
    • 映射记录
      • 分配的 Huge Object 会记录到 jemalloc 的全局管理表中,方便后续的释放与跟踪。
    • 释放机制
      • 当释放 Huge Object 时,jemalloc 会调用操作系统接口将对应内存区域返还给操作系统。
    • 特点
      • 针对超大内存块,分配和回收的开销较大,但能最大化减少外部碎片。
      • 直接与操作系统交互,绕过 Arena 的分区管理。
3. 内存释放
  • 小内存块(<56 KB)

    • 回收至 TCache
      • 当线程释放 Small Object 时,内存块会优先放回线程本地缓存(TCache)。
      • 如果 TCache 中对应的 bin 已满,则将内存块返还到线程绑定的 Arena 的 Bin 中。
    • 返还到 Bin
      • Bin 会记录释放的内存块并将其标记为空闲。
      • 空闲块加入 Bin 的空闲链表或 bitmap,等待下一次分配。
    • Trim 和合并(可选):
      • 如果 Bin 中的空闲块占用过多内存,jemalloc 会尝试将 Bin 内部的空闲块合并并返还至底层的 Extent(内存页管理模块)。
      • 进一步,Extent 可能将完全空闲的内存页返还给操作系统。
  • 中内存块(56KB-4MB)

    • 直接返还至 Arena
      • Large Object 的释放不会进入 TCache,直接返还到 Arena 管理的内存区域。
      • Arena 会将释放的内存块标记为空闲,并记录在其 Large Object 管理表中。
    • 尝试合并空闲块
      • Arena 会尝试将相邻的空闲内存块合并成更大的块,以减少外部碎片。
      • 如果合并后的内存块占用的内存页完全空闲,则返还给 Extent 管理模块。
    • 返还给操作系统(可选):
      • Extent 会检查是否可以将完全空闲的内存页返还给操作系统(通常通过 munmap)。
  • 大内存块(>4MB)

    • 直接返还给操作系统

      • Huge Object 的释放完全独立于 Arena,直接调用操作系统接口(如 munmap)释放内存。
      • 内存块会从全局 Huge Object 管理表中移除。

      无碎片问题

      • 由于 Huge Object 独立分配,释放后对应的内存区域会完全返还给操作系统,几乎没有外部碎片。

4)优点

1. 高并发性能
  • 多线程优化:通过线程本地缓存(TCache)和多 Arena 设计,极大减少了锁争用和线程间的内存竞争,适合高并发环境。
  • 分级锁管理:Arena 中的 Bin 独立加锁,按内存块大小分类,进一步减少锁争用。
2. 内存利用率高
  • 减少内存碎片:通过细粒度的 Bin 和 Extent 管理,以及动态合并空闲块,有效降低内存碎片。
  • 延迟释放:小对象优先保留在 TCache 和 Arena 中,减少频繁的系统调用(如 mmapmunmap),提升分配性能。
3. 灵活性强
  • 多 Arena 支持:允许动态调整 Arena 数量,适应不同的线程数量和并发需求。
  • 后台线程支持:通过后台线程回收未使用的内存块,优化长期运行的应用的内存占用。
4. 适应性广
  • 多种对象类型优化:根据内存分配请求的大小(Small、Large、Huge)采用不同的管理策略,适应多种使用场景。
  • 跨平台支持:jemalloc 在 Linux、Windows 和其他系统上表现一致,易于移植。
5. 开源与生态
  • 广泛应用:被 Redis、MongoDB、Facebook 等多个知名项目采用,可靠性和性能经过了大量实际场景验证。
  • 调试工具支持:内置多种调试和统计工具,便于开发者监控和优化内存使用。

5)缺点

1. 配置复杂
  • 参数配置繁多:jemalloc 提供了大量的配置选项,如 Arena 数量、背景线程策略等,对新手用户来说可能显得复杂。
  • 调优难度较高:需要针对具体场景进行细致调优,否则可能无法充分发挥其性能优势。
2. 内存占用较高
  • TCache 和 Arena 资源开销:为了减少并发锁竞争,jemalloc 使用了线程本地缓存和多 Arena,但也因此占用了更多的内存。
  • 延迟释放机制的代价:短期内可能无法及时释放未使用的内存,导致应用在内存紧张场景下占用更高。
3. 对 Huge Object 的性能较低
  • 直接调用系统接口:Huge Object 的分配和释放需要直接与操作系统交互,可能导致性能不如其他优化机制。
  • 外部碎片化风险:虽然 Huge Object 分配独立,但大块内存的管理可能在特定场景下引发碎片化问题。
4. 学习成本高
  • 实现复杂:jemalloc 的实现细节较为复杂,包括多级锁、TCache、Bin 和 Extent 等模块,需要深入学习才能完全掌握。
  • 生态门槛:虽然被广泛采用,但某些高级特性和调试工具对普通开发者来说可能不够友好。
5. 特定场景优化不足
  • 非高并发场景:在低并发或单线程场景中,jemalloc 的优势不明显,甚至可能因为其复杂的内存管理结构而引入额外开销。
  • 极小对象开销:对于极小内存块(如小于 8B)的分配,jemalloc 的内部对齐和管理策略可能导致额外的内存浪费。

五、综合对比

1)核心差异对比

1. 线程本地缓存机制
  • tcmallocjemalloc 都提供线程本地缓存机制,显著减少锁争用:
    • tcmallocThread Cache 简单高效,直接向中心链表申请内存。
    • jemallocTCache 更复杂,通过与 Arena 绑定的 Bin 提供分级管理,降低锁竞争。
  • ptmalloc 没有线程本地缓存,依赖 Arena 分区减少锁争用。
2. 碎片化管理
  • jemalloc 采用分级管理(Bin 和 Extent)和后台线程机制,碎片化控制最佳。
  • ptmalloc 简单的合并策略,碎片控制一般。
  • tcmalloc 优化并发性能,内存碎片管理相对不足。
3. 并发性能
  • jemalloc 的多 Arena 和分级锁设计,使其在高并发场景下表现最优。
  • tcmalloc 的无锁线程缓存在高并发时也表现优异。
  • ptmalloc 在高并发时锁争用较多,性能逊色。
  1. 适用场景
  • ptmalloc:适合一般场景,性能和碎片管理没有特定优化。
  • tcmalloc:适合高并发短生命周期场景,如 Web 服务、短生命周期对象分配。
  • jemalloc:适合高性能服务器和数据库等长生命周期、高并发需求场景。

2)详细对比

特性ptmalloctcmallocjemalloc
设计理念简单分区+锁机制分配内存基于线程本地缓存优化分配性能多层次内存分配架构,兼顾性能与碎片管理
分区机制多个分区(Arena),每个分区独立管理,线程分配到特定分区无分区,采用中心自由链表和线程缓存多个分区(Arena),按线程轮询绑定,支持动态扩展
线程本地缓存无线程本地缓存提供线程缓存(Thread Cache),减少锁竞争提供线程缓存(TCache),独立存储小对象
内存分配效率中等:分区减少竞争,但锁竞争仍然存在:线程缓存实现无锁分配,适合高并发场景:多分区+线程缓存+分级锁,分配效率高
内存碎片管理中等:简单的内存合并策略中等:以性能为主,碎片问题较为明显优秀:通过分级管理和后台线程清理,有效降低内存碎片
小对象分配分区按块大小划分链表,存在竞争线程缓存优先分配,无锁操作,快速TCache 优先分配小对象,避免锁竞争,补货机制高效
大对象分配直接调用 mmap 分配,性能较低直接调用系统接口,简单快速,但碎片化可能更严重通过 Extent 管理大对象,支持动态合并和切割,降低碎片化
并发性能中等:多个 Arena 减少竞争,但锁争用不可避免:线程缓存无锁,中心链表分区锁:TCache+多 Arena 独立管理,分级锁进一步优化
适用场景单线程或低并发场景,碎片管理要求不高高并发短生命周期场景,低碎片要求高并发长生命周期场景,要求高性能和低内存碎片
调试与统计基本无调试工具支持简单的统计和分析提供丰富的调试工具和运行时统计,便于优化
复杂性:实现简单,易于理解:引入线程缓存和中心链表,需调优:多层设计+复杂调优选项,学习成本较高
典型应用glibc 中默认内存分配器,适用于一般应用Google 内部项目,如 Bigtable 和 Chrome 浏览器等高性能数据库(Redis、MongoDB)和服务器应用

相关文章:

  • PHP序列化/反序列化漏洞原理
  • 杂记-LeetCode中部分题思路详解与笔记-HOT100篇-其三
  • 告别定时任务!用Dagster监听器实现秒级数据响应自动化
  • [ComfyUI]重磅升级,FLUX.1-dev-ControlNet-Union-Pro-2.0发布,更好用了
  • Java对接Dify API接口完整指南
  • 吴恩达深度学习复盘(19)XGBoost简介|神经网络与决策树
  • openai发布今天发布了o3和o4-mini。
  • Selenium 实现自动化分页处理与信息提取
  • 【JavaEE】Maven配置
  • (leetcode算法题)309. 买卖股票的最佳时机含冷冻期
  • 【音视频】音视频FLV合成实战
  • 界面开发框架DevExpress XAF实践:如何在Blazor项目中集成.NET Aspire?(一)
  • 拖拉拽效果加点击事件
  • 智慧交通内容及发展趋势概述
  • 第五章 SQLite数据库:6、SQLite 常用语法1
  • 【数据结构】AVL树
  • 主数据管理:企业数字化转型的 “数据基石“ 如何为 AI 筑基?
  • Google Mock(GMock):C++单元测试的高效模拟框架详解
  • D4707同步整流器:提升Flyback转换器效率的关键元件
  • 本地Ubuntu轻松部署高效性能监控平台SigNoz与远程使用教程
  • 中疾控:适龄儿童要及时、全程接种百白破疫苗
  • 中国海警就菲向非法“坐滩”仁爱礁军舰运补发表谈话
  • 印度空军为“阵风”战机换装国产导弹,以增强作战能力推动国防自主
  • 购房成本再降低!今年首次降息落地,30年期百万房贷月供将减少54元
  • 零跑汽车一季度营收破百亿元:净亏收窄至1.3亿元,毛利率14.9%创新高
  • 特朗普与普京就俄乌问题通话