内存分配器
在现代计算机系统中,内存分配器扮演着至关重要的角色,特别是在需要处理大量并发请求和内存分配的高性能应用中。以下是对内存分配器背景知识、常见内存分配器的对比以及内存碎片问题的讨论:
内存分配器背景知识
内存分配器负责管理程序的内存分配和回收。一个好的内存分配器可以显著提高应用程序的性能,减少内存碎片,并提供高效的内存使用。
常见内存分配器对比
-
jemalloc:
-
由Jason Evans在FreeBSD项目中开发,旨在提供高效的内存分配,特别是在高并发场景下。
-
广泛应用于Firefox、Redis、Rust、Netty等知名产品和技术中。
-
通过将内存分配粒度细分为Small、Large、Huge三个分类,并记录详细的元数据,有效减少内存碎片。
-
-
ptmalloc:
-
基于glibc实现的内存分配器,具有良好的兼容性。
-
采用每个线程独立的内存管理策略,但在多线程环境下可能导致内存无法共享,增加了内存开销。
-
-
tcmalloc:
-
由Google开发,全称为thread-caching malloc。
-
在Chrome、Safari等浏览器中得到应用。
-
为每个线程提供局部缓存,优化了小对象的分配速度,并采用自旋锁减少锁竞争。
-
内存分配器算法
内存分配器算法是操作系统和高性能应用中的关键技术。
内存碎片问题
内存碎片是指内存空间未能被充分利用的现象,主要分为内部碎片和外部碎片:
-
内部碎片:
-
由于内存按固定大小的Page分配,即使只需要少量内存,也会占用整个Page,导致Page内未被使用的字节形成内部碎片。
-
-
外部碎片:
-
在分配大内存块时产生,由于Page之间的空闲内存块分散,无法满足大内存请求,导致外部碎片。
-
动态内存分配(DMA)
动态内存分配,又称为堆内存分配,是一种按需分配内存的策略。操作系统在程序运行时根据请求动态地分配内存,分配的大小正好满足程序的需求。
-
元数据记录:为了跟踪分配的内存,DMA 会记录一些元数据。
-
空闲分区链:使用空闲分区链维护空闲内存,便于在内存分配时查找可用的空闲分区。
空闲分区链查找策略:
-
首次适应算法(First Fit):从空闲分区链的开始查找,找到第一个满足条件的空闲分区。
-
循环首次适应算法(Next Fit):从上次找到的空闲分区的下一个空闲分区开始查找。
-
最佳适应算法(Best Fit):总是选择能满足分配条件的最小空闲分区。
伙伴算法(Buddy System)
伙伴算法是一种基于2的次幂的内存分配策略,它将内存分为不同大小的块集合,每个集合中的块大小都是2的次幂。
-
内存块集合:使用双向链表连接同大小的内存块。
-
分配过程:根据请求的大小,找到对应的块集合,如果该集合中有空闲块,则进行分配;如果没有,则向上查找更大的块集合,并分割以满足请求。
伙伴算法的分配和释放:
-
分配:请求内存时,找到合适的块集合,分配所需大小的内存块。
-
释放:归还内存时,检查其伙伴块是否空闲,如果是,则合并两个块,重复检查直至伙伴块非空闲或达到最大大小。
Slab 算法
Slab 算法是一种用于管理小块内存分配的算法,它通过减少内存碎片和提高分配效率来优化内存使用。
-
Slab 分配器:将内存分为多个Slab,每个Slab包含固定大小的对象。
-
缓存行对齐:Slab 分配器通常将对象对齐到缓存行边界,以提高缓存效率。
总结
不同的内存分配器算法有各自的优势和适用场景。动态内存分配提供了灵活的内存管理方式,伙伴算法有效减少了外部碎片,而Slab 算法则专注于减少小块内存分配的开销和碎片。
jemalloc
jemalloc架构设计
jemalloc是一种高效的内存分配器,它采用了一系列优化技术来提高内存分配的速度和减少内存碎片。
jemalloc中的一些核心概念:
-
arena:arena是jemalloc中最重要的组成部分,它负责管理内存。每个线程都会被分配到一个或多个arena上,以轮询的方式进行内存分配。
-
bin:bin用于管理不同大小的内存单元。jemalloc使用Slab算法为小内存分配提供支持,因此会生成不同类别的内存块。
-
chunk:chunk是管理用户内存块的基本数据结构,以页面