1、动态内存分配DSA:
动态内存分配(DSA)在计算机中十分重要,其主要用于在程序运行时,根据需要分配和释放内存。
(1)、DSA的几个要点分别为:
-
内存管理方式:动态内存分配与静态内存分配 相对应,静态内存分配是在程序编译时为变量分配固定大小的内存空间,而动态内存分配是在程序运行时根据需要动态调整内存空间。
-
内存分配函数:编程语言通常提供内置的内存分配函数,如C/C++中的
malloc
、calloc
和C++中的new
,用于在堆(heap)中分配内存。这些函数返回一个指向分配内存的指针。 -
内存释放:动态分配的内存必须在使用完后进行释放,以防止内存泄漏。释放内存的函数是C/C++中的
free
和C++中的delete
。不释放已分配的内存会导致程序内存占用增加,最终可能导致系统性能下降。 -
内存泄漏:如果分配的内存在使用后没有被释放,就会发生内存泄漏。这会导致内存逐渐耗尽,最终可能导致程序崩溃或系统不稳定。
-
碎片问题:频繁的动态内存分配和释放可能导致内存碎片问题。分为外部碎片(内存块之间的未使用空间)和内部碎片(已分配内存块内部未使用的部分)。
-
动态数据结构:DSA允许在运行时创建动态数据结构,如链表、树和图。这些数据结构的大小和形状可以根据程序的需求动态变化。
-
性能开销:与静态内存分配相比,动态内存分配需要更多的系统开销,包括内存分配表的维护、碎片整理等。因此,在某些情况下,需要权衡是否使用动态内存分配。
-
错误处理:动态内存分配可能因为内存不足或其他原因失败。程序应该对分配失败进行适当的错误处理,避免崩溃或不可预料的行为。
(2)、DSA的几个关键点为:
- 快速响应时间(Fast response time):响应时间,理解为平时当申请分配size大小内存时,算法从池中查找到合适内存块的耗时,当然越短越好。
- 有界响应时间(Bounded response time):内存分配最坏的响应时间,比如使某空闲list算法,在一些情形下直到遍历到最后一个节点,才能发现合适的内存块,比正常找到慢非常多。这种边界性超长响应对一些实时要求高的系统或软件并不友好。
- 高效内存使用(Efficient Memory Use):内存的高效使用,这是最复杂且综合设计,必涉及内存碎片化的处理。比如内存池总有1000个字节内存,运行到一段时间后,500字节占用,500字节空闲,但占用与空闲的内存刚好单字节一一交错。这时你会发现,虽然池里面总共剩余500字节,你确连2个字节的连续内存都分配不出来。
(4)、常见的分配策略:
- Sequential Fit:最基础的算法,空闲链表法,所有内存空闲块都放到一个单向/双向list中,查找时会有边界响应问题。
- Segregated Fit:对Sequential Fit进行改进,对所有内存块按其大小区间放到不同list中,这些list首地址组成array,查找速度更快,dlmalloc算法使用该策略。
- Buddy System:对Segregate Fit算法的改进,更好的切割和合并效率,分配时效不错但内部碎片化问题比较严重,典型算法策略Binary Buddies,Fibonacci Buddies, Weighted Buddies,Double Buddies。
- Indexed Fit:基于使用高级结构索引空闲内存块。典型算法策略:基于平衡树的“Best Fit”,基于笛卡尔树存储的Stephenson’s Fast-Fit等。在一些情况下,比Segregated系列效果更好。
- Bitmap Fit:位图法,算是Indexed Fit算法的改进,使用小段内存表示的位图来确认内存块的占用或空闲,其通过降低缓存未命中的概率来提高响应时间。
2、TLSF算法:
(1)、TLSF简介:
TLSF(全称Two-Level Segregated Fit),两级隔离Fit内存分配器,是一款通用的动态内存分配,专门用于实时要求。
其有以下特点:
- 算法复杂度为O(1);
- 每次分配的开销极低(4字节);
- 低碎片化;
- 主要采用两级位图(Two-Level Bitmap)与分级空闲块链表(Segregated Free List)的数据结构管理动态内存池(memory pool)以及其中的空闲块(free blocks),用Good-Fit的策略进行分配。
(2)、分级空闲块链表(Segregated Free List):
分级空闲块链表(Segregated Free List)的设计思想是将空闲块按照大小分级,形成了不同块大小范围的分级,组内空闲块用链表链接起来。每次分配时先按分级大小范围查找到相应链表,再从相应链表挨个检索合适的空闲块,如果找不到,就在大小范围更大的一级查找,直到找到合适的块分配出去。
(3)、两级位图(Two-Level Bitmap):
使用位图的优势:
- 节省存储空间:用1-bit表示某个区间范围大小的空闲块是否存在;
- 位操作速度快:部分体系结构有加速特殊位操作的指令(如clz, ffs,fls)
TLSF采用了两级位图(Two-Level Bitmap)来管理不同大小范围的空闲块链(free block lists)。 上图中包含三个虚线矩形框分别是:
-
第一级位图(First-Level Bitmap),表示内存块的粗粒度范围,一般是2的幂次粒度(例如[ 2^n ~ 2^n+1 ])
-
第二级位图(Secend-Level Bitmap)是一个数组,一级位图中的每一位对应这个数组的一项,表示内存块的细粒度范围(例如[ 2^n + 02^n-2 ~ 2^n + 12^n-2 ])
-
第三个框是内存中真正的空闲内存块(free blocks)
(4)、Good-fit分配策略:
a、Best-fit(内部碎片最优化):
常规思路是:找到能满足内存请求大小的最小空闲块,就会有下面的流程(以搜索大小为69字节的空闲块为例)
-
基于位运算找到请求大小所在的第一级位图(First-Level bitmap)对应的粗粒度范围([ 64 ~ 128 ]),也就是二级位图的索引
-
在粗粒度范围内,根据二级位图索引检索第二级位图(Second-Level bitmap)得到细粒度范围([ 68 ~ 70 ])
-
如上图所示,沿着右下角空闲块链表可以检索到69字节的那一块是Best-fit
Best-fit策略最主要的问题还在于第三步,仍然需要检索对应范围的那一条空闲块链表,存在潜在的时间复杂度。
b、Good-fit:
Good-fit思路与Best-fit不同之处在于,Good-fit并不保证找到满足需求的最小空闲块,而是尽可能接近要分配的大小。
还以上述搜索大小为69字节的空闲块为例,Good-fit并不是找到[68 ~ 70]这一范围,而是比这个范围稍微大一点儿的范围(例如[71 ~ 73])。这样设计的好处就是[71 ~ 73]对应的空闲块链中每一块都能满足需求,不需要检索空闲块链表找到最小的,而是直接取空闲块链中第一块即可。整体上还不会造成太多碎片。