dlmalloc

本文介绍了douglea从1987年开始编写的dlmalloc动态内存分配器,探讨了其在兼容性、移植性、效率等方面的目标,并详细解析了boundary tags、binning等核心算法,以及locality和caching等特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://g.oswego.edu/dl/html/malloc.html

doug lea从87年开始写的dynamic memory allocator,到现在一直在发展,历时20+年的进化,成为一些版本的linux的内存分配器,在我见过的一些给力游戏engine中也有应用。

说到dynamic memory allocator,不禁会想到在ubi时候第一个项目,看见当时主程写的dynamic memory allocator,大惊:居然内存分配也要自己写的。里面各种hash,cache,对小内存的特殊处理,还根据游戏的特性专门做一些专区优化等。哈哈,那个项目真是让我印象深刻,看前辈的代码常常会有惊喜:我草,还可以这样写啊。里面的程序员很牛,大家嘻嘻哈哈连打再闹的就把事情完成的非常牛逼,以至于让我觉得做项目就这么简单。

-------------------------

goals

dl提到memory allocator需要在这些goals上取得平衡(清晰的思路我喜欢)

  • 兼容性
  • 好的移植性
  • 空间和时间上的效率
  • locality:分配内存时候尽可能保证在空间上相近,来提升cache performance
  • 好的可性能测试和debug能力
实际实现中,的确有遇到不少矛盾的地方,一些平衡工作就要做了。
------------------------
算法
  • boundary tags:在chunk的首尾都是有size information的,这样就使得两个重要功能成为可能:
    • 两个free chunk可以merge起来
    • 可以从任意位置开始遍历chunks
  • binning:把chunk以size进行分组,以类似hash表的方式表示,以best fit的方式进行分配
------------------------
locality
这个特性与其他的特性比如best fit是有冲突的,所以做一个优先级的排列,如果其他的都满足才会到这个优先级来尽量满足最近分配的东西在内存地址上也是近的。

------------------------
caching
merge和split chunk都是非常耗时的,所以可以做一些事情来降低这个损耗。
  • deferred merge,如果刚好free了一个,又alloc一个同样大小的,就免去了一次split和一次merge了
  • pre split,不用每一次alloc才split一点,可以在条件满足的情况下,一次split很多
  • 一些启发数算法,也就是有条件有规则的猜了,但是这种就是不能在所有情况下达到最好,但是启发数算法是有其存在道理的。
在后面的dlmalloc version中只是针对小size的内存进行caching。




dlmalloc是目前一个十分流行的内存分配器,其由Doug Lea(主页为http://gee.cs.oswego.edu/)从1987年开始编写,到目前为止,最新版本为2.8.3(可以从ftp://g.oswego.edu/pub/misc/malloc.c获取),由于其高效率等特点被广泛的使用(比如一些linux系统等用的就是dlmalloc或其变形,比如ptmalloc,主页为http://www.malloc.de/en/index.html)和研究(各位可以搜索关键字“GCspy”)。 dlmalloc的实现只有一个源文件(还有一个头文件),大概5000行,其内注释占了大量篇幅,由于有这么多注释存在的情况下,表面上看上去很容易懂,的确如此,在不追求细节的情况,对其大致思想的确很容易了解(没错,就只是了解而已),但是dlmalloc作为一个高品质的佳作,实现上使用了非常多的技巧,在实现细节上不花费一定的精力是没有办法深入理解其为什么这么做,这么做的好处在哪,只有当真正读懂后回味起来才发现它是如此美妙。 lenky0401个人博客将陆续推出对dlmalloc的解析(针对Doug Lea Malloc的最新版Version 2.8.3,未做说明的情况下以32位平台,8字节对齐作为假定平台环境设置考虑),由于个人水平有限,因此也不能完全保证对dlmalloc的所有理解都准备无误, 但是所有内容均出自个人的理解而并非存心妄自揣测来愚人耳目,所以如果读者发现其中有什么错误,请勿见怪,如果可以则请来信告之,并欢迎来信讨论(lenky0401@163.com)。 这一系列文章是lenky0401在看完dlmalloc的大部分代码后的再总结,不能保证对dlmalloc的整体完全把握,贴出这些只是希望可以提前收到对此有研究的网友的指点,以便在最后对这一系列文章整理而形成的PDF文档中错误能少一些。至于对于现在贴出来的内容中包含的错误给大家造成的不便提前说声抱歉。:) 描述的内容不会包含dlmalloc全部代码,但会将这其中涉及到的一些技巧尽量讲出,我相信对dlmalloc源代码不感兴趣的朋友也可以学到这些独立的技巧而使用在自己的编程实践中。:) 最后,转载请保留本博客地址连接[http://lenky0401.cublog.cn],谢谢。
<think>嗯,用户想了解dlmalloc内存分配器的工作原理。之前提供的引用材料提到了ptmalloc、dlmalloc以及它们的一些特性,比如分箱式内存管理、多线程支持、内存分配可能多出8字节等。用户现在明确要深入理解dlmalloc的机制。 dlmalloc是Doug Lea开发的内存分配器,虽然现在Linux主要用ptmalloc2(基于dlmalloc改进),但理解dlmalloc有助于掌握基础原理。根据引用[1][2],关键点包括分箱式管理(类似哈希桶)和多线程支持(ptmalloc2的优势)。引用[3]则提到分配时可能多出8字节的细节,这涉及chunk块管理。 需要从几个核心层面解释: 首先是chunk结构——dlmalloc用边界标记法,每个chunk头部存大小和状态位(P位指示前块是否在用),这样free时能快速合并相邻空闲块。引用[3]提到的"多出8字节"实际是chunk头部开销(用户申请10字节时,系统可能分配最小chunk如24字节)。 其次是分箱策略:小内存按大小分类到不同bin(如16字节的bin放16-31字节请求),大内存用树结构管理。引用[1]的"哈希桶"即指这种分箱机制。 最后是分配流程:先尝试精确匹配的bin,若无则找更大bin分割(分割后剩余部分进新bin),大内存直接走mmap。释放时合并相邻空闲块防止碎片,引用[3]的"不分割"例子说明碎片控制策略。 用户可能是系统开发者或学习者,需要避免过度技术术语。可以类比快递仓库:不同货架(bin)放不同尺寸包裹(chunk),新包裹优先放合适货架,取包裹后合并相邻空位。这样能呼应引用中的"分箱式管理"概念。 相关问题需覆盖优化(如ptmalloc的线程本地存储)、对比(dlmalloc vs ptmalloc)及实际影响(内存碎片),这样既延续引用材料又拓展应用场景。</think>好的,我们来详细解析 Doug Lea's Malloc (`dlmalloc`) 内存分配器的工作原理。它是许多现代分配器(如 `ptmalloc`)的基础。 ### dlmalloc 的核心原理和工作机制 `dlmalloc` 的核心目标是高效管理进程堆内存,响应用户的 `malloc` 和 `free` 请求,同时尽量减少内存碎片和提高分配/释放速度。其核心机制包括: 1. **内存块 (Chunk) 结构**: * `dlmalloc` 将堆内存视为一系列连续的 **内存块 (chunks)**。 * 每个 chunk 包含一个 **头部 (header)** 和可用的 **用户数据区**。用户通过 `malloc` 获得的是指向用户数据区的指针。 * 头部存储关键元数据: * **大小 (size)**: 当前 chunk 的总大小(包括头部和用户数据区)。通常以字节为单位,并且大小值通常是某个对齐值(如 8 字节)的倍数。这使得地址计算高效。 * **前一个块是否在使用中 (P - Previous in use)**: 一个标志位,指示内存中紧邻在当前 chunk **之前** 的那个 chunk 是否正在被使用(即尚未被 `free`)。这是实现边界标记法的关键。 * 结构示意(假设最小对齐为 8 字节): ``` ... | [前一个chunk] | [当前chunk 头部 (size | P-bit)] | [用户数据区 (malloc返回的指针指向这里)] | ... ``` * 当一个 chunk 被 `free` 时,其用户数据区的空间会被复用,存储一些链表指针(见下文 Bins)。 2. **边界标记法 (Boundary Tags)**: * 这是 `dlmalloc` 减少碎片的核心技术之一。 * 每个 chunk 的头部不仅存储自身的大小和状态位,其 **尾部**(或者更准确地说,下一个 chunk 的头部)也隐式地存储了 **当前** chunk 的大小信息(通过计算偏移)。 * **关键作用**: * **合并 (Coalescing)**: 当释放 (`free`) 一个 chunk 时,`dlmalloc` 可以快速检查其 **前一个** chunk(利用当前 chunk 头部的 P 位)和 **后一个** chunk(利用下一个 chunk 头部的隐式大小信息)是否也是空闲的。如果相邻 chunk 空闲,则立即将它们合并成一个更大的空闲 chunk。这大地减少了外部碎片。 * **分割 (Splitting)**: 当分配 (`malloc`) 内存时,如果找到一个比请求大的空闲 chunk,`dlmalloc` 可以将其分割成两部分:一个满足请求大小的 chunk 分配给用户,剩余部分形成一个新的空闲 chunk(只要剩余部分足够大能容纳一个最小 chunk 的头部)。 3. **分箱管理 (Binning)**: * `dlmalloc` 维护多个链表(称为 **bins**),用于组织不同大小的空闲 chunk,以实现快速查找。 * **小内存块 (Small Bins)**: * 管理固定大小的 chunk(例如,每个 bin 负责 16 字节的间隔:16-31, 32-47, 48-63, ... 字节)。 * 通常实现为 **双向循环链表 (doubly-linked lists)**。链表操作(插入、删除)是 O(1) 的。 * 分配时,找到对应请求大小的 bin。如果 bin 非空,直接取出第一个 chunk(精确匹配或稍大,无需分割)。释放时,直接放回对应 bin。 * **大内存块 (Large Bins)**: * 管理大于小 bin 最大尺寸的空闲 chunk。 * 每个 bin 覆盖一个范围(例如,512-1023, 1024-2047, ... 字节)。 * 链表中的 chunk 按大小 **排序**(通常从大到小或从小到大)。分配时需要遍历链表寻找最合适(Best-fit 或 Good-fit)的 chunk。释放时插入到合适位置保持有序。 * **Unsorted Bin**: * 一个临时的 bin。当用户 `free` 一个 chunk 时,它不会立即放入小 bin 或大 bin,而是先放入 unsorted bin。 * 在后续的 `malloc` 调用中,`dlmalloc` 会优先搜索 unsorted bin,尝试满足请求。如果满足则分配;如果不满足(大小不合适),则将其从 unsorted bin 中移除,并放入对应的小 bin 或大 bin 中。 * 这相当于一种缓存策略,能提高最近释放的 chunk 被快速重用的机会,减少立即分类的开销。 * **Top Chunk (Wilderness)**: * 堆内存区最顶端的一个特殊 chunk。 * 当所有 bins 中都没有合适的空闲 chunk 时,`dlmalloc` 会尝试从 top chunk 中分割一部分来满足请求。 * 如果 top chunk 不够大,`dlmalloc` 会通过系统调用(如 `sbrk` 或 `mmap`)向操作系统申请更多内存来扩展 top chunk。 4. **分配算法 (malloc)**: 1. 如果请求大小很小(在某个阈值内,如 256 字节): * 计算对应的 **小 bin** 索引。 * 检查该 bin 是否为空。 * 如果非空,取出第一个 chunk,返回其用户数据区指针(精确匹配,速度快)。 2. 如果请求大小属于小 bin 范围但对应的 bin 为空,或者请求大小属于大 bin 范围: * 首先检查 **unsorted bin**(最近释放的 chunk),尝试找到一个大小匹配的 chunk。如果找到,将其移除并返回(或分割后返回)。 * 如果 unsorted bin 没有合适的,则搜索对应的 **大 bin**(或更大的小 bin),寻找满足请求的最小 chunk(Best-fit)。找到后分割,返回用户部分,剩余部分放入 unsorted bin(如果够大)。 3. 如果以上步骤都失败: * 尝试从 **top chunk** 分割。如果 top chunk 足够大,分割并返回用户部分。 * 如果 top chunk 不够大,调用系统调用(`sbrk` 或 `mmap`)扩展堆,增大 top chunk,然后从新的 top chunk 中分割。 4. 如果请求非常大(超过某个阈值,如 128KB),`dlmalloc` 通常会绕过上述机制,直接使用 `mmap` 分配一块独立的内存区(以避免碎片化主堆),并在释放时直接用 `munmap` 归还给操作系统。 5. **释放算法 (free)**: 1. 检查传入的指针是否有效(例如,是否对齐)。 2. 根据指针找到对应的 chunk 头部。 3. 检查前一个 chunk 是否空闲(通过当前 chunk 头部的 P 位)。如果空闲,将其从 bin 中取出,与当前 chunk **合并**。 4. 检查后一个 chunk 是否空闲(通过后一个 chunk 的头部信息)。如果空闲,将其从 bin 中取出,与当前(可能已合并)的 chunk **合并**。 5. 将最终合并得到的空闲 chunk 放入 **unsorted bin**。 6. **优化策略**: * **Fast Bins**: 这是 `ptmalloc` 对 `dlmalloc` 的重要扩展(虽然现代 `dlmalloc` 版本也可能引入)。管理一些非常小的 chunk(如 16, 24, 32, ..., 80 字节)。释放时,chunk 被放入 fast bin,但 **不立即合并**(P 位不清除)。分配对应小内存时,优先从 fast bin 取,速度快(单链表)。这优化了小内存高频分配释放的性能,但牺牲了部分碎片控制。 * **线程本地存储 (TLS) / Arena**: `ptmalloc` 的核心改进,`dlmalloc` 本身是单线程的。`ptmalloc` 引入了多个 `arena`(每个 arena 本质是一个独立的堆,包含自己的 bins、top chunk 等结构)。每个线程默认绑定一个 arena,大部分分配释放操作都在线程自己的 arena 中进行,避免了全局锁竞争,大地提升了多线程性能[^2]。 ### 引用材料的相关点解释 * **引用[1]**: 提到的 "分箱式内存管理机制"、"类似哈希桶结构" 直接对应了 dlmalloc 的核心机制 **Binning**。 * **引用[2]**: 说明了 `dlmalloc` 是 `ptmalloc` 的前身,`ptmalloc` 通过增加 **多线程支持(Arena)** 取代了 `dlmalloc` 在 Linux 中的地位。 * **引用[3]**: 解释了内存分配开销和最小分配单位: * "块头、对齐等开销" 指的是每个 chunk 的 **头部元数据** 和 **地址对齐** 要求。 * "malloc分配的内存也可能会比应用程序实际应分配内存多" 指的是 `dlmalloc` 分配的最小 chunk 大小(例如,包含头部和对齐后是 24 字节)。即使用户申请 10 字节,系统也会分配一个 24 字节的 chunk(头部 8 字节 + 用户数据 16 字节,或类似结构)。用户得到指向这 16 字节的指针。 * "10,这种情况时,dlmalloc分配的内存将多出8个字节。由于8个字节不足以组成一个chunk块,因此也就没有必要进行分割" 描述的场景是:假设最小 chunk 是 16 字节(头部 8 + 用户 8)。用户申请 10 字节,需要的最小用户空间是 10 字节。最小 chunk 需要 16 字节(8 头 + 8 用户)。但 8 字节用户空间 < 10,不够。所以分配器必须分配下一个尺寸的 chunk,例如 24 字节(8 头 + 16 用户)。用户得到 16 字节中的前 10 字节使用。此时,如果这个 chunk 被分割,剩下的部分应该是 24 - (8头 + 10用户) = 6 字节。但这 6 字节连一个最小的 chunk 头部(8 字节)都放不下,因此分配器**不会分割**,而是将整个 24 字节的 chunk 分配给用户(用户用了 10 字节,实际分配了 24 字节,多出 14 字节)。引用中的 "多出8个字节" 可能是指用户实际需要 10,但分配器因结构限制至少给 16(用户区),所以多给 6 字节用户空间?或者是对最小 chunk 结构的简化描述。核心概念是:**分配器有最小分配单位(一个 chunk 的大小),如果用户申请量接近但不满足分割条件(剩余空间不足以形成新 chunk),分配器会将整个 chunk 分配出去,导致内部碎片**。 ### 总结 `dlmalloc` 是一个高效、可移植的内存分配器,其核心在于: 1. **Chunk 结构**:管理内存的基本单位,包含元数据。 2. **边界标记法**:实现 O(1) 时间的相邻空闲 chunk 合并,减少外部碎片。 3. **分箱管理 (Bins)**:使用不同策略(小 bin 精确匹配,大 bin 排序查找,unsorted bin 缓存)组织空闲 chunk,加速查找。 4. **Top Chunk**:作为堆扩展的缓冲区和最后分配来源。 5. **分配/释放流程**:结合 bin 查找、分割、合并和系统调用扩展堆。 `ptmalloc` 在 `dlmalloc` 的基础上,主要增加了 **Fast Bins** 和 **Arena (TLS)** 机制,以优化多线程环境下的性能[^2]。理解 `dlmalloc` 是理解现代 `glibc` 内存管理(`ptmalloc`)的基础。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值