简介分配策略 是指一套数据结构和算法,它们用来表示和管理堆,实现动态分配、释放和重新分配内存等内存管理功能。
AIX malloc 子系统支持以下分配策略:
默认的 (Yorktown) 分配策略
Watson 分配策略
Malloc 3.1 分配策略
默认分配策略概述AIX 中的默认分配策略也称为 Yorktown 策略。它按照 Cartesian 二分法检索树格式以节点的形式维护堆中的空闲空间。
节点按照地址从左到右排列(地址向右增加),按照长度从上到下排列(因此子节点不会比其父节点大)。
图 1. Yorktown 策略的 Cartesian 二分法检索树
什么时候应该使用 Yorktown在 AIX 上默认启用 Yorktown。它是所有分配策略中使用最广泛、最稳定的,因此适用于几乎所有情况。
因为它采用树的形式,树中有不同大小的节点,所以对分配的块大小没有限制。
用以下命令启用 Yorktown:
export MALLOCTYPE=Yorktown |
可以通过两种方法检查是否启用了 Yorktown:
使用 DBX 子命令 malloc
运行命令 echo $MALLOCTYPE
Yorktown 分配算法分配的总内存 = roundup (n + prefix_size, alignment),其中:
n 等于用户请求的字节数
prefix_size 等于元数据信息
首先,删除空闲树中(大于或等于请求的块大小的)地址最低的节点。如果这个节点大于请求的内存量,就把它分为两块。多余的那一块返回给树,插入在空闲树中的适当位置。然后把请求的块返回给调用者。
Yorktown 释放算法在释放内存时,需要把释放的节点插入到空闲树中。这采用标准的根插入算法。搜索树中的每个节点并与释放的节点进行比较,如果释放的节点的地址与任何其他节点相邻,它就与那个节点合并。合并相邻节点在一定程度上可以避免碎片化,因为如果树中散布着大量空闲节点,即使它们的地址相邻,连续分配也无法使用它们。
Yorktown 重新分配算法如果重新分配的块大于原来的块,那么把原来的块返回给空闲树,以便与相邻的节点合并(如果可能的话)。然后,按请求的大小分配一个新的块。把数据从原来的块转移到新的块中,把新的块返回给调用者。原来的块被释放,不再有效。如果重新分配的块小于原来的块,那么把它分割开,多余的块返回给空闲树,请求的块返回给调用者。
Watson 分配策略概述Watson 分配策略在两个单独的 “红黑树” 中以节点的形式维护堆中的空闲空间:
按地址排序
按大小排序
必须适当地维护这两个树以确保一致性和正确性。
图 2. Watson 分配策略中使用的基于 “按地址排序” 的红黑树
地址树中每个节点的地址都比左边子树中的节点低,比右边子树中的节点高。
图 3. Watson 分配策略中使用的基于 “按大小排序” 的红黑树
大小树中的每个节点都比左边子树中的节点小,比右边子树中的节点大。
什么时候应该使用 Watson因为 Watson 在红黑树中维护空间,所以与其他策略相比,它提供更高效的插入和搜索等树操作。
用以下命令启用它:
export MALLOCTYPE=Watson |
Watson 分配算法与 Yorktown 策略相似,Watson 搜索大小树,寻找符合请求的最小的块。如果在大小树中找到的块正好是请求的大小,那么从大小树和地址树中删除这个块并返回给调用者。
如果在空闲树中没有找到足够大的块,那么使用 sbrk() 系统调用扩展进程堆,把扩展后的块添加到大小树和地址树中,然后像以前一样继续分配。
Watson 释放算法节点首先返回到地址树的根上。在树中移动,检查此节点是否与树中的任何节点地址相邻。如果有相邻节点,那么释放的节点与树中的节点合并。以这种方式合并节点可以避免堆内存碎片化。
Watson 重新分配算法如果重新分配的块大于原来的块,那么把原来的块返回给空闲树,以便与相邻的节点合并(如果可能的话)。然后,按请求的大小分配一个新的块。把数据从原来的块转移到新的块中,把新的块返回给调用者。如果重新分配的块小于原来的块,那么把它分割开,多余的块返回给空闲树,请求的块发送给调用者。
malloc 3.1 分配策略概述
在 malloc 3.1 中,以散列 bucket 的形式维护堆内存,每个 bucket 指向一个链表,每个链表只包含特定大小的节点或块。使用以下公式计算块的大小(i 表示 bucket 编号):
大小 = 2 ^ (i + 4) |
图 4. 在 malloc 3.1 中以散列 bucket 的形式维护堆内存
用以下命令启用 malloc:
export MALLOCTYPE=3.1 (对于 32 位程序) export MALLOCTYPE=3.1_64BIT (对于 64 位程序) |
Malloc 3.1 分配算法
当从空闲池分配块时,首先使用以下公式把请求的字节数转换为 bucket 数组索引:
needed = requested + 8 If needed <= 16, then bucket = 0 If needed >16, then bucket = (log(needed)/log(2) rounded down to the nearest integer) - 3 |
bucket 指向的链表中每个块的大小为 2 ^ (bucket + 4)。如果 bucket 中的链表是空的,就使用 sbrk() 子例程向其中添加块。如果空闲链表非空,那么把链表开头的块返回给调用者。链表中的下一个块成为新的链表头。
Malloc 3.1 释放算法当把内存块返回给空闲池时,像分配时一样计算 bucket 索引。然后,把释放的节点添加到这个 bucket 的空闲链表的开头。
Malloc 3.1 重新分配算法在重新分配时,比较请求的大小和已分配块的大小。因为每个 bucket 处理的块大小差异很大,新的块大小可能属于与原来的块相同的 bucket 的处理范围。在这种情况下,返回相同的块。如果所需的大小大于现有的块,那么释放这个块,从另一个 bucket 分配一个新的块,把数据从原来的块转移到新的块中。
Malloc 3.1 的局限性malloc 分配策略的效率比默认分配策略低,对于大多数情况不建议采用这种策略。这种策略把分配请求的大小向上取整到下一个可用的块大小。因此,分配的内存可能比需要量多。
有时候,应用程序可能会不知不觉地依赖于 malloc 3.1 分配策略的副作用。例如,假设一个程序会错误地覆盖 malloc 已经分配的字节。当使用 malloc 3.1 分配策略时,它可能能够正确地运行,因为从本质上说 3.1 策略可能分配超过请求量的内存。当采用其他分配策略时,这个程序应该会运行失败。
但是,在某些情况下,3.1 策略的性能可能比其他分配算法好。这是因为它最初分配了超过实际请求量的内存,所以可以减少重新分配期间的数据转移开销。
其他 malloc 选项其他 malloc 选项包括:
malloc multiheap
pool allocation
malloc buckets
malloc multiheap
在默认情况下,malloc 子系统把整个进程堆当作单一实体。在多线程环境中,多个线程访问同一个堆。在这种场景中,使用单一堆是非常低效的,因为在为一个线程服务时它会取得堆锁,其他线程不得不等待轮到它们访问堆。对于这种情况,这个选项是有意义的。
使用以下命令启用 multiheap 选项:
MALLOCOPTIONS=[multiheap:n] | [considersize] |
下面说明选项的作用:
multiheap:nmalloc multiheap 可分配的最大堆数量是 32。可以通过指定 1 到 32 的数字指定允许分配的堆数量。
considersize
在默认情况下,malloc multiheap 选择下一个可用的堆。如果指定 considersize 选项,malloc multiheap 会使用不同的算法,选择空闲空间足以处理请求的堆。
malloc multiheap 的局限性因为单一堆实际上被分割为许多个堆(最多 32 个),不必要地启用 malloc multiheap 会导致严重的碎片化。它还会导致系统看起来内存耗尽的情况,因为可用的内存碎片化,没有连续的可用内存。
因为 considersize 选项需要额外的处理,使用此选项可能会在一定程度上降低进程的性能。
对于 malloc 3.1 和用户定义的分配策略,不支持 multiheap 选项。
pool allocation对于小于或等于 512 字节的内存块,AIX malloc 为 malloc 的实际后端功能提供更高效的前端。pool allocation 为每个线程创建它自己的 malloc 池,以此避免与其他线程发生锁争用。目前,对于这个分配器,每个进程的最大池大小限制为 512MB。
使用以下命令启用 pool allocation:
MALLOCOPTIONS=pool<:max_size> |
这会创建固定数量的大小增加的池。32 位应用程序有 128 个池,64 位应用程序有 64 个池。
如果应用程序的一个线程分配内存,而其他线程释放内存,那么 pool allocation 实际上是无效的。
malloc bucketsmalloc buckets 是 Yorktown 分配策略的扩展。当应用程序有许多小的分配请求时,经常使用 malloc buckets。它的主要作用是避免过分碎片化,因为它把所有小内存块集中在一个地方。此选项只能与 Yorktown 分配策略一起使用。
使用以下命令启用 malloc buckets:
MALLOCOPTIONS=buckets,number_of_buckets:n |
bucket 分配器只能处理最大 512 字节的分配请求,更大的分配请求自动地转发给 Yorktown malloc。
结束语本文讨论了各种 AIX 内存分配方案的基本原理及其特点。全面了解各种分配方案有助于您选择最适合自己应用程序的分配方案,从而实现最好的执行效率和性能。