查看源码 persistent_term (erts v15.2)

持久化项。

此模块类似于 ets,它提供了一个用于存储 Erlang 项的存储,可以以恒定时间访问,但不同之处在于 persistent_term 经过高度优化,可以在读取项时具有高性能,但牺牲了写入和更新项的性能。当持久化项被更新或删除时,将运行全局垃圾回收过程,扫描所有进程以查找已删除的项,并将其复制到仍然使用它的每个进程中。因此,persistent_term 适合存储频繁访问但很少或从不更新的 Erlang 项。

警告

持久化项是一项高级功能,并非 ETS 表的通用替代品。在使用持久化项之前,请务必充分了解更新或删除持久化项对系统性能的影响。

项查找(使用 get/1)以恒定时间完成,无需任何锁,并且该项不会复制到堆中(就像存储在 ETS 表中的项一样)。

存储或更新项(使用 put/2)与已创建的持久化项的数量成正比,因为保存键的哈希表将被复制。此外,项本身也将被复制。

当删除(使用 erase/1)或被另一个项替换(使用 put/2)一个(复杂)项时,将启动全局垃圾回收。其工作方式如下:

  • 系统中所有进程都将被调度运行,扫描其堆以查找已删除的项。虽然这种扫描相对较轻量,但如果进程过多,系统可能会变得响应迟缓,直到所有进程都扫描完其堆。
  • 如果已删除的项(或其任何部分)仍被进程使用,该进程将进行一次主要的(全量)垃圾回收,并将该项复制到该进程中。但是,最多一次只有两个进程将被调度进行这种垃圾回收。

原子和适合一个机器字的其他项的删除经过特殊优化,可以避免进行全局 GC。但不建议过于频繁地使用此类值更新持久化项,因为每次更新持久化项时,保存键的哈希表都会被复制。

以下是一些适合使用持久化项的示例:

  • 存储必须可供所有进程轻松访问的配置数据。
  • 存储 NIF 资源的引用。
  • 存储用于高效计数器的引用。
  • 存储一个原子,以指示日志级别或是否启用调试。

存储大型持久化项

持久化项的当前实现使用文字 分配器,该分配器也用于 BEAM 代码中的文字(常量项)。默认情况下,在 BEAM 代码和持久化项中为文字保留 1 GB 的虚拟地址空间。可以使用启动模拟器时的 +MIscs 选项 更改为文字保留的虚拟地址空间量。

以下示例说明如何将为文字保留的虚拟地址空间增加到 2 GB (2048 MB):

    erl +MIscs 2048

使用持久化项的最佳实践

建议使用类似 ?MODULE{?MODULE,SubKey} 的键,以避免名称冲突。

与创建许多小型持久化项相比,更倾向于创建少量大型持久化项。存储持久化项的执行时间与已存在的项的数量成正比。

使用与已有的值相同的值更新持久化项经过特殊优化,可以快速执行操作;因此,无需比较旧值和新值,如果值相等,则避免调用 put/2

当原子或其他适合一个机器字的项被删除时,不需要全局 GC。因此,可以将原子作为值的持久化项更频繁地更新,但请注意,更新此类持久化项仍然比读取它们昂贵得多。

如果该项不适合一个机器字,则更新或删除持久化项将触发全局 GC。进程将像往常一样被调度,但所有进程都将一次性变为可运行,这将使系统响应迟缓,直到所有进程都运行并扫描其堆以查找已删除的项。一种最大限度地减少对响应能力影响的方法是在更新或删除持久化项之前,尽量减少节点上的进程数。在系统处于高峰负载时避免更新项也是明智之举。

如果该持久化项将来可能被删除或更新,则避免将检索到的持久化项存储在进程中。如果进程在项被删除时持有对持久化项的引用,则该进程将被垃圾回收,并将该项复制到该进程中。

避免一次更新或删除多个持久化项。每个删除的项都会触发其自身的全局 GC。这意味着删除 N 个项会使系统的响应迟缓时间比删除单个持久化项长 N 倍。因此,要同时更新的项应收集到一个更大的项中,例如,map 或元组。

示例

以下示例说明如何通过为每个调度程序设置一个 ETS 表来最大限度地减少 ETS 表的锁争用。ETS 表的表标识符存储为单个持久化项

    %% There is one ETS table for each scheduler.
    Sid = erlang:system_info(scheduler_id),
    Tid = element(Sid, persistent_term:get(?MODULE)),
    ets:update_counter(Tid, Key, 1).

总结

类型

任何 Erlang 项。

任何 Erlang 项。

函数

删除键为 Key 的持久化项的名称。

检索所有持久化项的键和值。

检索与键 Key 关联的持久化项的值。

检索与键 Key 关联的持久化项的值。

以 map 形式返回有关持久化项的信息。

将值 Value 存储为持久化项,并将其与键 Key 关联。

类型

链接到此类型

key()

查看源码 (自 OTP 21.2 起)
-type key() :: term().

任何 Erlang 项。

链接到此类型

value()

查看源码 (自 OTP 21.2 起)
-type value() :: term().

任何 Erlang 项。

函数

链接到此函数

erase(Key)

查看源码 (自 OTP 21.2 起)
-spec erase(Key) -> Result when Key :: key(), Result :: boolean().

删除键为 Key 的持久化项的名称。

如果存在键为 Key 的持久化项,则返回值将为 true;如果没有与该键关联的持久化项,则返回 false

如果存在与键 Key 关联的先前持久化项,则当 erase/1 返回时,将启动全局 GC。请参阅描述

链接到此函数

get()

查看源码 (自 OTP 21.2 起)
-spec get() -> List when List :: [{key(), value()}].

检索所有持久化项的键和值。

这些键将被复制到调用 get/0 的进程的堆中,但值不会。

链接到此函数

get(Key)

查看源码 (自 OTP 21.2 起)
-spec get(Key) -> Value when Key :: key(), Value :: value().

检索与键 Key 关联的持久化项的值。

查找将以恒定时间完成,并且该值不会复制到调用进程的堆中。

如果未存储具有键 Key 的项,则此函数将引发 badarg 异常。

如果调用进程保留了持久化项的值,并且该持久化项将来被删除,则该项将被复制到该进程中。

链接到此函数

get(Key, Default)

查看源码 (自 OTP 21.3 起)
-spec get(Key, Default) -> Value when Key :: key(), Default :: value(), Value :: value().

检索与键 Key 关联的持久化项的值。

查找将以恒定时间完成,并且该值不会复制到调用进程的堆中。

如果未存储具有键 Key 的项,则此函数返回 Default

如果调用进程保留了持久化项的值,并且该持久化项将来被删除,则该项将被复制到该进程中。

链接到此函数

info()

查看源码 (自 OTP 21.2 起)
-spec info() -> Info
              when
                  Info :: #{count := Count, memory := Memory},
                  Count :: non_neg_integer(),
                  Memory :: non_neg_integer().

以 map 形式返回有关持久化项的信息。

该 map 具有以下键:

  • count - 持久化项的数量。

  • memory - 所有持久化项使用的内存总数(以字节为单位)。

链接到此函数

put(Key, Value)

查看源代码 (自 OTP 21.2 起)
-spec put(Key, Value) -> ok when Key :: key(), Value :: value().

将值 Value 存储为持久化项,并将其与键 Key 关联。

如果值 Value 等于先前为该键存储的值,则 put/2 将不会执行任何操作并快速返回。

如果存在与键 Key 关联的先前的持久项,则在 put/2 返回时会启动全局 GC。请参阅描述