内存分配算法
1.了解内存对齐
创建My_Struct实例要用多少内存呢?-----》计算总共是 6 字节, ------》打印显示 12字节。 为什么会有这么大的差异呢?------答案------》发生了 “内存对齐”
2.了解内存分配器
3.了解内存碎片
linux操作系统中表示一块内存块(具体里面都有那些变量AI了解一下)
4.了解搞一个内存分配器要考虑的一些问题
5. 了解分离的空闲链表【数据结构】
维护多个空闲链表,其中每个链表中的空闲块大小“大致”差不多
举例QQ应用想找一块,12字节内存用用(大家注意,图片错误点是 k 其实是字节哈,我写错了)
另一种情景:c组 9~16区间没有满足12字节的空闲块
对上述数据结构(数组+链表)的实践、
A.简单分离存储算法
B.分离适配算法
Linux生态系统中的众多内存分配器
(1). 用户态(应用程序级)内存分配器
再次描述什么是用户态内存分配器
首先,我们要知道当你写应用程序使用 malloc 和 free 是C库暴露给程序员的标准接口(API)。而内存分配器则是隐藏在API之下的实现机制。它的核心任务是:
• 处理程序通过 malloc、calloc、realloc 等函数发出的内存申请请求。
• 向操作系统(通过 brk、sbrk 或 mmap 等系统调用)申请大块内存(俗称“堆”)。
• 将申请来的大块内存进行高效地管理、分割、分配给应用程序。
• 在用户调用 free 时回收内存,并尝试将相邻的空闲内存合并,以便后续使用。
• 同时要追求高效(速度快)和减损(避免内存碎片,节省空间)。
(1.1)ptmalloc分配器(Linux操作系统用户态默认分配器)
当人们说“malloc 和 free 使用了 ptmalloc 分配器”,其准确含义是——》在 Linux 等使用 glibc 的系统上,你调用的 malloc 和 free 函数,其底层是由 ptmalloc 这个分配器来提供实现的。
ptmalloc 就是 glibc 中默认使用的内存分配器。 当我们说“glibc 的 malloc 和 free”时,在绝大多数情况下,指的就是 ptmalloc 的行为。 ptmalloc 的由来和历史
- dlmalloc:最早有一个非常著名且优秀的内存分配器,叫做 Doug Lea‘s Malloc,简称 dlmalloc。它由 Doug Lea 编写,具有良好的单线程性能。
- ptmalloc:随着多线程程序的普及,dlmalloc 在多线程环境下的性能瓶颈显现(因为所有线程操作一个共享堆,需要全局锁,导致激烈竞争)。Wolfram Gloger 在 dlmalloc 的基础上进行了改进,增加了对多线程的支持,特别是引入了 Per-Thread Arena(线程私有竞技场) 的概念。这个改进版的分配器就被命名为 ptmalloc,即 “pthread malloc”。
- 被 glibc 采纳:GNU C Library (glibc) 项目看中了 ptmalloc 的优秀性能,尤其是其多线程能力,便将其集成作为自家 malloc/free 的默认实现。所以,自从 glibc 2.3 版本以来,ptmalloc 就一直是 glibc 的默认内存分配器。
ptmalloc 与 glibc 的关系
您可以这样理解它们的关系:
- glibc 是一个完整的标准库实现,它提供了 malloc、printf、strcpy 等成百上千个标准函数。
- ptmalloc 是 glibc 这个庞大库中,专门负责实现 malloc, free, calloc, realloc 这一组内存管理函数的那个模块。
ptmalloc 的核心设计思想:Arena
ptmalloc 解决多线程性能问题的核心是 Arena(竞技场)。
- 主竞技场:在单线程程序中,只有一个“主竞技场”(Main Arena)。所有内存分配请求都由它服务,它通过 brk() 系统调用来增长堆空间。
- 线程私有竞技场:在多线程程序中,当一个新的线程首次调用 malloc 时,ptmalloc 会尝试为它创建一个新的私有竞技场。这样,该线程后续的内存分配和释放操作大部分都在自己的竞技场中完成,无需与其他线程竞争全局锁。
- 缓解锁竞争:只有当线程的私有竞技场内存不足时,它才会需要锁住主竞技场来申请一大块新的内存。通过这种方式,ptmalloc 极大地减少了线程间为申请内存而等待锁的次数,从而提升了多线程程序的性能。
(1.2)jemalloc分配器
- 起源:最初由 Jason Evans 为 FreeBSD 开发,现在也被广泛应用于 Linux、Windows 等平台。是 Mozilla Firefox、Redis、Rust 语言标准库等众多知名软件的首选。
- 设计特点:
- 注重碎片避免和多线程可扩展性。
- 使用多个称为 “arena” 的内存区域(类似 ptmalloc),但设计更为激进和高效。
- 对每个 CPU 核心(或线程)使用独立的 arena,极大减少了线程间的锁竞争。
- 提供了丰富的监控和调优接口,可以详细分析内存使用情况。
- 适用场景:多线程服务器应用程序、长时间运行的服务(如数据库、缓存系统),这些场景对内存碎片和性能稳定性要求极高。
(1.3)tcmalloc (Thread-Caching Malloc)分配器
- 起源:由 Google 开发,是其高性能编程工具集 gperftools 的一部分。
- 设计特点:
- 极致追求分配速度,尤其是小对象的分配。
- 核心思想是为每个线程创建一个本地缓存。小内存分配(<32KB)可以直接从线程本地缓存中获取,完全无锁,速度极快。
- 大内存分配则直接使用全局堆。
- 也提供了强大的堆分析工具(Heap Profiler)。
- 适用场景:对性能极其敏感的应用,特别是那些会频繁分配大量小对象的应用。在 Google 的内部基础设施和很多追求极致 QPS 的互联网服务中广泛使用
(1.4)scalable malloc (lockless malloc)分配器
- 特点:这类分配器(如 microsoft/snmalloc, locklessinc.com 的分配器)专注于极致的多核可扩展性,使用无锁(lock-free)或更细粒度的同步原语来确保在超多核机器上几乎无限的线性扩展能力
(1.5)拓展
(2)内核态(操作系统内核)内存分配器
Linux 内核自身也需要动态管理内存,它有一套完全独立于用户态的内存分配机制。常见的包括:
1.SLAB 分配器
- 这是 Linux 内核早期和经典的分配器。
- 它为内核中频繁分配和释放的小对象(如 task_struct, inode 等)创建了对象缓存。
- 优点是可以高效利用内存并对热门对象进行缓存,提高分配速度。
2. SLUB 分配器
- SLUB(The Unqueued Slab Allocator)是 SLAB 的继承者和当前默认的内核内存分配器。
- 它简化了 SLAB 的设计,减少了代码复杂性和元数据开销,在大多数情况下提供了比 SLAB 更好的性能。
- 它是现在绝大多数 Linux 发行版的默认内核分配器。
3. SLOB 分配器
- 一个非常简单的分配器,设计目标是极低的内存开销,而非性能。
- 主要用于嵌入式系统或内存极度受限(<64MB)的特殊环境。
- 由于其简单的首次适应算法,容易产生碎片,性能较差,一般不用于通用系统
用户态下虚拟内存的分配和释放
在 Linux 中,用户空间程序使用 malloc() 申请堆内存时,底层并不是直接调用系统调用,而是通过 glibc 的内存分配器(ptmalloc) 来管理内存。这个分配器会根据请求的大小和当前内存池状态,选择不同的方式向内核申请内存。主要有以下两种系统调用方式:
现实情况:glibc 的“内存池”机制, 确实,程序不会每次 malloc() 都触发系统调用(brk 或 mmap),那样性能会极差。
glibc 的 ptmalloc 内存分配器实现了一个用户空间的内存池(arena),批量向内核申请内存,“说白了,就是提前给你程序申请好一些内存块,不够了再去系统调用申请一些内存块”,然后自己管理、切割、复用,减少系统调用次数。
(1)ptmalloc分配器中的分离空闲链表(bins)
(1.1)small bins(小箱子组合)
这个小组内分配的内存不需要考虑分割
(1.2)large bins(大箱子组合)
(1.3)fast bins(快速箱子组合)
程序在运行时会京城需要申请和释放一些较小的内存空间。当分配器合并了相邻的几个小的chunk之后,
也许马上就会有另一个小块内存的请求,这样分配器又需要从大的空闲内存中切分出一块,这样无疑是比较低效的(容易内存抖动),所以ptmalloc分配过程中引入了 fast bins
(1.4)unsorted bin(未分类的箱子)
last remainder :是从large bins中分割出来的剩余内存块
总结:
(2)ptmalloc内存分配器工作大体流程
个人学习总结,个人观点,如有错误,请多多指导,谢谢!