查看源代码 erts_alloc

Erlang 运行时系统内部内存分配器库。

描述

erts_alloc 是一个 Erlang 运行时系统内部的内存分配器库。erts_alloc 为 Erlang 运行时系统提供了一系列的内存分配器。

分配器

存在以下分配器

  • temp_alloc - 用于临时分配的分配器。

  • eheap_alloc - 用于 Erlang 堆数据的分配器,例如 Erlang 进程堆。

  • binary_alloc - 用于 Erlang 二进制数据的分配器。

  • ets_alloc - 用于 ets 数据的分配器。

  • driver_alloc - 用于驱动程序数据的分配器。

  • literal_alloc - 用于 Erlang 代码中常量项的分配器。

  • sl_alloc - 用于预期为短生命周期的内存块的分配器。

  • ll_alloc - 用于预期为长生命周期的内存块的分配器,例如 Erlang 代码。

  • fix_alloc - 用于一些常用的固定大小数据类型的快速分配器。

  • std_alloc - 用于大多数未通过上述任何其他分配器分配的内存块的分配器。

  • sys_alloc - 这通常是在特定操作系统上使用的默认 malloc 实现。

  • mseg_alloc - 内存段分配器。它被其他分配器用于分配内存段,并且仅在具有 mmap 系统调用的系统上可用。已解除分配的内存段在被销毁之前会在段缓存中保留一段时间。当分配段时,如果可能,会使用缓存的段,而不是创建新的段。这可以减少系统调用的次数。

sys_allocliteral_alloctemp_alloc 始终启用且无法禁用。mseg_alloc 如果可用且使用了它的分配器被启用,则始终启用。所有其他分配器都可以启用或禁用。默认情况下,所有分配器都启用。当分配器被禁用时,将使用 sys_alloc 代替禁用的分配器。

erts_alloc 库的主要思想是将以不同方式使用的内存块分离到不同的内存区域中,以减少内存碎片。通过在查找频繁分配的内存块的好位置时减少工作量,而不是那些不太频繁分配的内存块,可以实现性能提升。

alloc_util 框架

在内部,一个名为 alloc_util 的框架用于实现分配器。sys_allocmseg_alloc 不使用此框架,因此以下内容不适用于它们。

分配器管理多个称为载体的区域,内存块放置在其中。载体放置在单独的内存段中(通过 mseg_alloc 分配)或堆段中(通过 sys_alloc 分配)。

  • 多块载体用于存储多个块。
  • 单块载体用于存储一个块。
  • 大于单块载体阈值(sbct)参数值的块放置在单块载体中。
  • 小于参数 sbct 值的块放置在多块载体中。

通常,分配器会创建一个“主多块载体”。主多块载体永远不会被解除分配。主多块载体的大小由参数 mmbcs 的值确定。

通过 mseg_alloc 分配的多块载体的大小基于以下参数确定

  • 最大多块载体大小的值 (lmbcs)
  • 最小多块载体大小 (smbcs)
  • 多块载体增长阶段 (mbcgs)

如果 nc 是分配器管理的多块载体的当前数量(不包括主多块载体),则此分配器分配的下一个 mseg_alloc 多块载体的大小大约为 smbcs+nc*(lmbcs-smbcs)/mbcgs(当 nc <= mbcgs 时),以及 lmbcs(当 nc > mbcgs 时)。如果参数 sbct 的值大于参数 lmbcs 的值,则分配器可能必须创建大于参数 lmbcs 值的多块载体。通过 mseg_alloc 分配的单块载体的大小调整为整页。

通过 sys_alloc 分配的载体的大小基于 sys_alloc 载体大小 (ycs) 参数的值确定。载体的大小是满足请求的参数 ycs 值的最小倍数。

空闲块的合并始终立即执行。空闲块中的边界标签(头部和尾部)会被使用,这使得合并的时间复杂度保持不变。

分配器用于多块载体的内存分配策略可以使用参数 as 进行配置。以下策略可用

  • 最佳匹配 - 策略:找到满足请求块大小的最小块。

    实现:使用平衡的二叉搜索树。时间复杂度与 log N 成正比,其中 N 是空闲块大小的数量。

  • 地址顺序最佳匹配 - 策略:找到满足请求块大小的最小块。如果找到多个块,则选择地址最低的块。

    实现:使用平衡的二叉搜索树。时间复杂度与 log N 成正比,其中 N 是空闲块的数量。

  • 地址顺序首次匹配 - 策略:找到满足请求块大小的地址最低的块。

    实现:使用平衡的二叉搜索树。时间复杂度与 log N 成正比,其中 N 是空闲块的数量。

  • 地址顺序首次匹配载体最佳匹配 - 策略:找到可满足请求块大小的地址最低的载体,然后使用“最佳匹配”策略在该载体中查找块。

    实现:使用平衡的二叉搜索树。时间复杂度与 log N 成正比,其中 N 是空闲块的数量。

  • 地址顺序首次匹配载体地址顺序最佳匹配 - 策略:找到可满足请求块大小的地址最低的载体,然后使用“地址顺序最佳匹配”策略在该载体中查找块。

    实现:使用平衡的二叉搜索树。时间复杂度与 log N 成正比,其中 N 是空闲块的数量。

  • 年龄顺序首次匹配载体地址顺序首次匹配 - 策略:找到可满足请求块大小的最旧载体,然后使用“地址顺序首次匹配”策略在该载体中查找块。

    实现:使用平衡的二叉搜索树。时间复杂度与 log N 成正比,其中 N 是空闲块的数量。

  • 年龄顺序首次匹配载体最佳匹配 - 策略:找到可满足请求块大小的最旧载体,然后使用“最佳匹配”策略在该载体中查找块。

    实现:使用平衡的二叉搜索树。时间复杂度与 log N 成正比,其中 N 是空闲块的数量。

  • 年龄顺序首次匹配载体地址顺序最佳匹配 - 策略:找到可满足请求块大小的最旧载体,然后使用“地址顺序最佳匹配”策略在该载体中查找块。

    实现:使用平衡的二叉搜索树。时间复杂度与 log N 成正比,其中 N 是空闲块的数量。

  • 良好匹配 - 策略:尝试找到最佳匹配,但在有限的搜索中找到的最佳匹配中进行选择。

    实现:该实现使用分段空闲列表,并具有最大块搜索深度(在每个列表中),以快速找到良好的匹配。当最大块搜索深度较小时(默认为 3),此实现的时间复杂度是恒定的。可以使用参数 mbsd 配置最大块搜索深度。

  • A 匹配 - 策略:不搜索匹配项,仅检查一个空闲块以查看其是否满足请求。此策略仅用于临时分配。

    实现:检查空闲列表中的第一个块。如果它满足请求,则使用它,否则将创建一个新的载体。该实现的时间复杂度是恒定的。

    从 ERTS 5.6.1 开始,模拟器拒绝在 temp_alloc 之外的其他分配器上使用此策略。这是因为它只会给其他分配器带来问题。

除了上述普通分配器之外,一些预分配器用于某些特定的数据类型。这些预分配器在运行时系统启动时为某些数据类型预分配固定数量的内存。只要预分配的内存可用,就会使用它。当没有预分配的内存可用时,内存将分配在普通分配器中。这些预分配器通常比普通分配器快得多,但只能满足有限数量的请求。

影响 erts_alloc 的系统标志

警告

仅当您确定自己在做什么时才使用这些标志。不合适的设置可能会导致严重的性能下降,甚至在运行期间的任何时候都可能导致系统崩溃。

内存分配器系统标志具有以下语法: +M<S><P> <V>,其中 <S> 是标识子系统的字母,<P> 是参数,<V> 是要使用的值。这些标志可以作为命令行参数传递给 Erlang 模拟器(erl(1))。

影响特定分配器的系统标志使用大写字母作为 <S>。以下字母用于分配器

  • B: binary_alloc
  • D: std_alloc
  • E: ets_alloc
  • F: fix_alloc
  • H: eheap_alloc
  • I: literal_alloc
  • L: ll_alloc
  • M: mseg_alloc
  • R: driver_alloc
  • S: sl_alloc
  • T: temp_alloc
  • Y: sys_alloc

mseg_alloc 的配置标志

  • +MMamcbf <size> - 绝对最大缓存错误匹配(以千字节为单位)。如果内存段缓存中的段大小超过请求大小的值,则不会重复使用该段。默认为 4096

  • +MMrmcbf <ratio> - 相对最大缓存坏适配(以百分比表示)。如果内存段缓存中的段的大小超出请求大小的相对最大缓存坏适配百分比,则不会重用该段。默认为 20

  • +MMlp on|off - 启用使用大页(有时称为巨页或超级页)来映射内存段分配。大页通过减少 TLB 压力来提高性能,但有时分配成本可能很高,或者只能以尽力而为的方式分配。目前仅影响在超级载体中分配的内存段。默认为 off

  • +MMsco true|false - 设置超级载体专用标志。默认为 true。当使用超级载体且此标志为 true 时,mseg_alloc 仅在超级载体中创建载体。请注意,alloc_util 框架可以创建 sys_alloc 载体,因此,如果您希望所有载体都在超级载体中创建,则还需要通过传递 +Musac false 来禁用 sys_alloc 载体的使用。当该标志为 false 时,当超级载体已满时,mseg_alloc 会尝试在超级载体外部创建载体。

    注意

    并非所有系统都支持将此标志设置为 false。然后将忽略该标志。

  • +MMscrfsd <amount> - 设置超级载体保留的空闲段描述符。默认为 65536。此参数确定为超级载体使用的空闲段描述符保留的内存量。如果系统用完了用于空闲段描述符的保留内存,则会使用其他内存。但是,这可能会导致碎片问题,因此您需要确保永远不会发生这种情况。可以从调用 erlang:system_info({allocator, mseg_alloc}) 的结果的 erts_mmap 元组部分检索使用的最大空闲段描述符数量。

  • +MMscrpm true|false - 设置超级载体保留物理内存标志。默认为 true。当此标志为 true 时,将在创建时为整个超级载体一次性保留物理内存。保留之后将保持不变。当此标志设置为 false 时,仅在创建时为超级载体保留虚拟地址空间。系统会在超级载体中创建载体时尝试保留物理内存,并在超级载体中销毁载体时尝试取消保留物理内存。

    注意

    保留物理内存的含义很大程度上取决于操作系统及其配置方式。例如,Linux 上不同的内存过提交设置会极大地改变行为。

    并非所有系统都可能支持将此标志设置为 false。然后将忽略该标志。

  • +MMscs <size in MB> - 设置超级载体大小(以 MB 为单位)。默认为 0,即默认禁用超级载体。超级载体是虚拟地址空间中一个大的连续区域。mseg_alloc 始终尝试在超级载体中创建新的载体(如果存在)。请注意,alloc_util 框架可以创建 sys_alloc 载体。有关更多信息,请参见 +MMsco

  • +MMmcs <amount> - 最大缓存段数。存储在内存段缓存中的最大内存段数。有效范围为 [0, 30]。默认为 10

用于配置 sys_alloc 的标志

  • +MYe true - 启用 sys_alloc

    注意

    sys_alloc 无法禁用。

  • +MYtt <size> - 剪切阈值大小(以千字节为单位)。这是堆顶部(由 sbrk 分配)保留的最大可用内存量(未释放给操作系统)。当堆顶部的可用内存量超过剪切阈值时,malloc 会将其释放(通过调用 sbrk)。剪切阈值以千字节为单位指定。默认为 128

    注意

    只有当模拟器与 GNU C 库链接并使用其 malloc 实现时,此标志才有效。

  • +MYtp <size> - 顶部填充大小(以千字节为单位)。这是当调用 sbrk 从操作系统获取更多内存时,malloc 分配的额外内存量。默认为 0

    注意

    只有当模拟器与 GNU C 库链接并使用其 malloc 实现时,此标志才有效。

基于 alloc_util 的分配器配置标志

如果 u 用作子系统标识符(即,<S> = u),则所有基于 alloc_util 的分配器都会受到影响。如果 BDEFHILRSTX 用作子系统标识符,则仅影响特定的分配器标识符。

  • +M<S>acul <utilization>|de - 放弃载体利用率限制。有效的 <utilization> 是一个范围为 [0, 100] 的整数,表示以百分比表示的利用率。当使用 > 0 的利用率值时,允许分配器实例放弃多块载体。如果传递 de(默认启用)而不是 <utilization>,则使用推荐的非零利用率值。所选值取决于分配器类型,并且可以在 ERTS 版本之间更改。默认为 de,但这将来可能会更改。

    当分配器实例中的内存利用率低于所使用的利用率值时,将放弃载体。一旦放弃载体,就不会在其内部进行新的分配。当分配器实例获得增加的多块载体需求时,它首先尝试从另一个分配器实例获取放弃的载体。如果无法获取放弃的载体,它将创建一个新的空载体。当获取了放弃的载体后,它将像普通载体一样运行。此功能对所使用的 分配策略有特殊要求。只有策略 aoffaoffcbfaoffcaobfageffcaoffm、ageffcbfageffcaobf 支持放弃的载体。

    此功能还需要启用多个线程特定实例。启用此功能时,如果尚未启用,则会启用多个线程特定实例,如果当前策略不支持放弃的载体,则启用 aoffcbf 策略。可以在基于 alloc_util 框架的所有分配器上启用此功能,除了 temp_alloc(这将毫无意义)。

  • +M<S>acfml <bytes> - 放弃载体空闲块最小限制。有效的 <bytes> 是一个正整数,表示块大小限制。载体中最大的空闲块必须至少为 bytes 大小,才能放弃该载体。默认值为零,但将来可能会更改。

    另请参见 acul

  • +M<S>acnl <amount> - 放弃载体数量限制。有效的 <amount> 是一个正整数,表示每个分配器实例放弃的最大载体数量。默认为 1000,这实际上会禁用该限制,但将来可能会更改。

    另请参见 acul

  • +M<S>acful <utilization>|de - 放弃载体空闲利用率限制。当载体的利用率低于此限制时,erts_alloc 会指示操作系统,载体中未使用的内存可以被其他操作系统进程重复用于分配。在 Unix 上,这是通过在未使用的内存区域上调用 madvise(..., ..., MADV_FREE) 来完成的,在 Windows 上,则是通过调用 VirtualAlloc(..., ..., MEM_RESET, PAGE_READWRITE) 来完成的。默认为 0,这意味着不会将任何内存标记为可由操作系统重用。

    有效的 <utilization> 是一个范围为 [0, 100] 的整数,表示以百分比表示的利用率。如果此值大于 acul 限制,则会将其降低到当前的 acul 限制。如果传递 de(默认启用)而不是 <utilization>,则使用推荐的非零利用率值。所选值取决于分配器类型,并且可以在 ERTS 版本之间更改。

    另请参见 acul

  • +M<S>as bf|aobf|aoff|aoffcbf|aoffcaobf|ageffcaoff|ageffcbf|ageffcaobf|gf|af - 分配策略。以下策略有效

    • bf (最佳匹配)
    • aobf (地址顺序最佳匹配)
    • aoff (地址顺序首次匹配)
    • aoffcbf (地址顺序首次匹配载体最佳匹配)
    • aoffcaobf (地址顺序首次匹配载体地址顺序最佳匹配)
    • ageffcaoff (年龄顺序首次匹配载体地址顺序首次匹配)
    • ageffcbf (年龄顺序首次匹配载体最佳匹配)
    • ageffcaobf (年龄顺序首次匹配载体地址顺序最佳匹配)
    • gf (良好匹配)
    • af (一个匹配)

    请参阅 alloc_util 框架部分中分配策略的描述。

  • +M<S>asbcst <size> - 绝对单块载体收缩阈值(以千字节为单位)。当位于 mseg_alloc 单块载体中的块收缩时,如果未使用的内存量小于此阈值,则载体保持不变,否则载体将收缩。另请参见 rsbcst

  • +M<S>atags true|false - 向每个分配的块添加一个小标签,其中包含有关它是什么以及谁分配它的基本信息。使用 instrument 模块来检查此信息。

    启用时,每次分配的运行时开销为两个字。这在未来可能会随时更改。

    对于 binary_allocdriver_alloc,默认值为 true,对于其他分配器类型,默认值为 false

  • +M<S>cp B|D|E|F|H||L|R|S|@|: - 设置分配器使用的载体池。内存载体仅在使用相同载体池的分配器实例之间迁移。存在以下载体池名称:

    • B - 与 binary_alloc 关联的载体池。

    • D - 与 std_alloc 关联的载体池。

    • E - 与 ets_alloc 关联的载体池。

    • F - 与 fix_alloc 关联的载体池。

    • H - 与 eheap_alloc 关联的载体池。

    • L - 与 ll_alloc 关联的载体池。

    • R - 与 driver_alloc 关联的载体池。

    • S - 与 sl_alloc 关联的载体池。

    • @ - 与整个系统关联的载体池。

    除了将载体池名称作为参数值传递外,您还可以传递 :。 通过传递 : 而不是载体池名称,分配器将使用与其自身关联的载体池。 通过传递命令行参数 "+Mucg :",所有具有关联载体池的分配器都将使用与其自身关联的载体池。

    载体池和分配器之间的关联非常松散。 这些关联或多或少只是为了获得所需载体池的数量的名称,以及可以通过 : 值轻松识别的载体池的名称。

    此标志仅对具有关联载体池的分配器有效。 除此之外,对分配器使用的载体池没有任何限制。

    目前,每个具有关联载体池的分配器默认使用其自身关联的载体池。

  • +M<S>e true|false - 启用分配器 <S>

  • +M<S>lmbcs <size> - 最大 (mseg_alloc) 多块载体大小(以千字节为单位)。 有关如何决定 mseg_alloc 多块载体大小的说明,请参见 alloc_util 框架 部分。在 32 位 Unix 风格的操作系统上,此限制不能设置 > 64 MB。

  • +M<S>mbcgs <ratio> - (mseg_alloc) 多块载体增长阶段。 有关如何决定 mseg_alloc 多块载体大小的说明,请参见 alloc_util 框架 部分。

  • +M<S>mbsd <depth> - 最大块搜索深度。 仅当为分配器 <S> 选择最佳匹配策略时,此标志才有效。 当使用最佳匹配策略时,空闲块会放置在隔离的空闲列表中。 每个空闲列表都包含特定大小范围内的块。 最大块搜索深度设置了在搜索满足请求的合适块时,在空闲列表中检查的最大块数限制。

  • +M<S>mmbcs <size> - 主多块载体大小。 设置分配器 <S> 的主多块载体的大小。 主多块载体通过 sys_alloc 分配,并且永远不会被释放。

  • +M<S>mmmbc <amount> - 最大 mseg_alloc 多块载体数量。 通过分配器 <S> 通过 mseg_alloc 分配的最大多块载体数量。 达到此限制后,将通过 sys_alloc 分配新的多块载体。

  • +M<S>mmsbc <amount> - 最大 mseg_alloc 单块载体数量。 通过分配器 <S> 通过 mseg_alloc 分配的最大单块载体数量。 达到此限制后,将通过 sys_alloc 分配新的单块载体。

  • +M<S>ramv <bool> - 重新分配始终移动。 启用后,重新分配操作或多或少会转换为分配、复制、释放序列。 这通常会减少内存碎片,但会降低性能。

  • +M<S>rmbcmt <ratio> - 相对多块载体移动阈值(以百分比表示)。 当多块载体中的块收缩时,如果释放的内存大小与先前大小之比大于此阈值,则移动该块,否则该块会在当前位置收缩。

  • +M<S>rsbcmt <ratio> - 相对单块载体移动阈值(以百分比表示)。 当单块载体中的块收缩为小于参数 sbct 的值时,如果未使用的内存比率小于此阈值,则该块在单块载体中保持不变,否则会将其移动到多块载体中。

  • +M<S>rsbcst <ratio> - 相对单块载体收缩阈值(以百分比表示)。 当 mseg_alloc 单块载体中的块收缩时,如果未使用的内存比率小于此阈值,则该载体保持不变,否则将收缩该载体。另请参见 asbcst

  • +M<S>sbct <size> - 单块载体阈值(以千字节为单位)。 大于此阈值的块放置在单块载体中。 小于此阈值的块放置在多块载体中。 在 32 位 Unix 风格的操作系统上,此阈值不能设置 > 8 MB。

  • +M<S>smbcs <size> - 最小 (mseg_alloc) 多块载体大小(以千字节为单位)。 有关如何决定 mseg_alloc 多块载体大小的说明,请参见 alloc_util 框架 部分。

  • +M<S>t true|false - 分配器的多个线程特定实例。 默认行为是 NoSchedulers+1 个实例。 每个调度器都使用其自己的无锁实例,而其他线程使用一个公共实例。

    在 ERTS 5.9 之前,可以配置比调度器更少的线程特定实例。 然而,现在已经不可能了。

alloc_util 配置标志

所有基于 alloc_util 的分配器都受影响。

  • +Muycs <size> - sys_alloc 载体大小。 通过 sys_alloc 分配的载体以 sys_alloc 载体大小的倍数分配。 但对于主多块载体和在内存短缺期间分配的载体来说,情况并非如此。

  • +Mummc <amount> - 最大 mseg_alloc 载体数量。 放置在单独内存段中的最大载体数量。 达到此限制后,新的载体将放置在从 sys_alloc 检索的内存中。

  • +Musac <bool> - 允许 sys_alloc 载体。 默认为 true。 如果设置为 false,则使用 alloc_util 框架的分配器永远不会创建 sys_alloc 载体。

literal_alloc 的特殊标志

  • +MIscs <size in MB> - literal_alloc 超级载体大小(以 MB 为单位)。 在 64 位架构上为 Erlang 代码中的字面量项保留的 *虚拟* 地址空间量。 默认为 1024(即 1 GB),通常足够。 此标志在 32 位架构上被忽略。

检测标志

  • +M<S>atags - 向每个已分配的块添加一个小的标记,其中包含有关它是什么以及谁分配它的基本信息。 有关更完整的说明,请参见 +M<S>atags

注意

当启用仿真器的检测功能时,仿真器会使用更多内存并运行速度变慢。

其他标志

  • +Mea min|max|r9c|r10b|r11b|config - 选项

    • min - 禁用所有可以禁用的分配器。

    • max - 启用所有分配器(默认)。

    • r9c|r10b|r11b - 将所有分配器配置为在相应的 Erlang/OTP 版本中配置的方式。 这些最终会被删除。

  • +Mlpm all|no - 锁定物理内存。 默认为 no,即不锁定物理内存。 如果设置为 all,则运行时系统进行的所有内存映射都将锁定到物理内存中。 如果设置为 all,则如果不支持此功能、用户没有足够的权限或不允许用户锁定足够的物理内存,则运行时系统将无法启动。 如果达到用户锁定的内存量限制,则运行时系统也会因内存不足而失败。

  • +Mdai max|<amount> - 设置使用的脏分配器实例的数量。 默认为 0。 也就是说,默认情况下不使用任何实例。 实例的最大数量等于系统上的脏 CPU 调度器的数量。

    默认情况下,每个普通调度器线程对于每个分配器都有自己的分配器实例。 系统中的所有其他线程(包括脏调度器)对于每个分配器共享一个实例。 通过启用脏分配器实例,脏调度器将获取并使用它们自己的一组分配器实例。 请注意,这些实例并非每个脏调度器独有,而是由脏调度器共享。 使用的实例越多,这些分配器实例上的锁竞争风险就越小。 但是,内存消耗会随着脏分配器实例数量的增加而增加。

备注

这里只介绍了一些默认值。 有关当前使用的设置和分配器的当前状态的信息,请参阅 erlang:system_info(allocator)erlang:system_info({allocator, Alloc})

注意

这些标志中的大多数都高度依赖于实现,并且可能会在不事先通知的情况下更改或删除。

erts_alloc 不必严格使用传递给它的设置(它甚至可以忽略它们)。

另请参阅

erl(1), erlang, instrument