Erlang/OTP 25 亮点

2022 年 5 月 18 日 · 作者:Kenneth Lundin

OTP 25 终于发布了。这篇文章将介绍我最激动的一些新特性。

您可以在这里下载包含所有更改的自述文件:Erlang/OTP 25 自述文件。或者,像往常一样,查看您感兴趣的应用程序的版本说明。例如这里:Erlang/OTP 25 - Erts 版本说明 - 13.0 版

今年的亮点有

mapslists 模块中的新函数 #

受用户建议的启发,我们在 stdlibmapslists 模块中引入了新函数。

maps:groups_from_list/2,3 #

简而言之,我们可以说这个函数接受一个元素列表并对它们进行分组。结果是一个 map #{Group1 => [Group1Elements], GroupN => [GroupNElements]}

让我们来看一些 shell 中的示例

> maps:groups_from_list(fun(X) -> X rem 2 end, [1,2,3]).
#{0 => [2], 1 => [1, 3]}

提供的 fun 会计算输入列表中每个元素 XX rem 2,然后将元素分组到一个 map 中,其中 X rem 2 的结果作为键,对应的元素作为该键的列表值。

> maps:groups_from_list(fun erlang:length/1, ["ant", "buffalo", "cat", "dingo"]).
#{3 => ["ant", "cat"], 5 => ["dingo"], 7 => ["buffalo"]}

在上面的示例中,输入列表中的字符串根据其长度分组到一个 map 中。

groups_from_list 还有一个变体,它带有一个额外的 fun,可以在将值放入其组之前对其进行转换。

> maps:groups_from_list(fun(X) -> X rem 2 end, fun(X) -> X*X end, [1,2,3]).
#{0 => [4], 1 => [1, 9]}

在上面的示例中,列表中的元素 X 根据 X rem 2 的计算结果进行分组,但存储在组中的值是元素乘以自身 (X * X)。

> maps:groups_from_list(fun erlang:length/1, fun lists:reverse/1, ["ant", "buffalo", "cat", "dingo"]).
#{3 => ["tna","tac"],5 => ["ognid"],7 => ["olaffub"]}

在上面的示例中,输入列表中的字符串根据其长度进行分组,并在存储在组中之前被反转。

有关更多详细信息,请参阅 maps:groups_from_list/2 文档。

lists:enumerate/1,2 #

接受一个元素列表,并返回一个新列表,其中每个元素都与其在原始列表中的位置相关联。返回一个新列表,其中包含 {I, H} 形式的元组,其中 IH 在原始列表中的位置。枚举从 1 开始,并在每一步递增 1。

示例

> lists:enumerate([a,b,c]).
[{1,a},{2,b},{3,c}]

还有一个 enumerate/2 函数,可用于将初始数字设置为 1 以外的其他数字。请参见以下示例

> lists:enumerate(10, [a,b,c]).
[{10,a},{11,b},{12,c}]

有关更多详细信息,请参阅 lists:enumerate/1 文档。

lists:uniq/1,2 #

从列表中删除重复项,同时保留元素的顺序。保留每个元素的第一次出现。我们已经有了 lists:usort,它也可以删除重复项,但返回一个已排序的列表。

示例

> lists:uniq([3,3,1,2,1,2,3]).
[3,1,2]
> lists:uniq([a, a, 1, b, 2, a, 3]).
[a, 1, b, 2, 3]

lists:uniq/2 允许用户使用 fun 来指定如何确定列表中两个元素是否相等。在下面的示例中,提供的 fun 只是测试两个元组的第一个元素是否相等。

示例

> lists:uniq(fun({X, _}) -> X end, [{b, 2}, {a, 1}, {c, 3}, {a, 2}]).
[{b, 2}, {a, 1}, {c, 3}]

有关更多详细信息,请参阅 lists:uniq/1 文档。

可选特性和新的 maybe_expr 特性 #

可选特性是一种新的机制和概念,可以在不给不使用它的用户带来麻烦的情况下引入和测试新的潜在不兼容的特性(语言或运行时)。

对于语言特性,其目的是它们可以在每个模块中激活,而不会对未激活它们的模块产生影响。

让我们以新的 maybe_expr 特性为例。

在模块 my_experiment 中,该特性被激活并像这样使用

-module(my_experiment).
-export([foo/1]).

%% Enable the feature maybe_expr in this module only
%% Makes maybe a keyword which might be incompatible
%% in modules using maybe as a function name or an atom
-feature(maybe_expr,enable). 
foo() ->
  maybe
    {ok, X} ?= f(Foo),
    [H|T] ?= g([1,2,3]),
    ...
  else
    {error, Y} ->
        {ok, "default"};
    {ok, _Term} ->
        {error, "unexpected wrapper"}
  end.

编译器将注意到 maybe_expr 特性已启用,并将正确处理 maybe 构造。在生成的 .beam 文件中,还将记录该模块已启用该特性。

启动 Erlang 节点时,必须启用特定特性(或全部特性),否则不允许加载具有该特性的 .beam 文件。

erl -enable-feature maybe_expr

或者

erl -enable-feature all

有关更多详细信息,请参阅 Erlang 参考手册中的 特性部分

新的 maybe_expr 特性 EEP-49 #

Fred Hebert 早在 2018 年就提出了 EEP-49 “基于值的错误处理机制”,现在它终于作为新特性概念中的第一个特性实现。

maybe ... end 构造类似于 begin ... end,因为它用于将多个不同的表达式组合为单个块。但有一个重要的区别,即 maybe 块不会导出其变量,而 begin 会导出其变量。

引入了一种新的表达式类型(表示为 MatchOrReturnExprs),它仅在 maybe ... end 表达式中有效

maybe
    Exprs | MatchOrReturnExprs
end

MatchOrReturnExprs 定义为具有以下形式

Pattern ?= Expr

此定义意味着 MatchOrReturnExprs 仅允许在 maybe ... end 表达式的顶层。

?= 运算符获取 Expr 返回的值,并将其与 Pattern 进行模式匹配。

如果模式匹配,则 Pattern 中的所有变量都会绑定到本地环境中,并且该表达式等效于成功的 Pattern = Expr 调用。如果值不匹配,则 maybe ... end 表达式将直接返回失败的表达式。

存在一个特殊情况,我们将其扩展为以下形式

maybe
    Exprs | MatchOrReturnExprs
else
    Pattern -> Exprs;
    ...
    Pattern -> Exprs
end

此形式的存在是为了捕获 MatchOrReturnExprs 中不匹配的表达式,以处理失败的匹配,而不是返回它们的值。在这种情况下,未处理的失败匹配将引发 else_clause 错误,否则与 case_clause 错误相同。

此扩展形式可用于正确识别和处理同一构造中的成功和不成功匹配,而不会冒混淆成功和失败路径的风险。

根据此处描述的结构,最终的表达式可能如下所示

maybe
    Foo = bar(),            % normal exprs still allowed
    {ok, X} ?= f(Foo),
    [H|T] ?= g([1,2,3]),
    ...
else
    {error, Y} ->
        {ok, "default"};
    {ok, _Term} ->
        {error, "unexpected wrapper"}
end

有关更多详细信息,请参阅 Erlang 参考手册中的 maybe 部分

动机 #

使用 maybe 构造,可以减少深度嵌套的条件表达式,并使在实际应用中发现的混乱模式变得不必要。它还在实现函数时提供了更好的关注点分离。

减少嵌套 #

在 Erlang 中常见的一种模式是深度嵌套的 case ... end 表达式,用于检查复杂的条件。

例如,采用以下从 Mnesia 中获取的代码

commit_write(OpaqueData) ->
    B = OpaqueData,
    case disk_log:sync(B#backup.file_desc) of
        ok ->
            case disk_log:close(B#backup.file_desc) of
                ok ->
                    case file:rename(B#backup.tmp_file, B#backup.file) of
                        ok ->
                            {ok, B#backup.file};
                        {error, Reason} ->
                            {error, Reason}
                    end;
                {error, Reason} ->
                    {error, Reason}
            end;
        {error, Reason} ->
            {error, Reason}
    end.

代码嵌套的程度使得必须为变量引入较短的别名(OpaqueData 重命名为 B),并且一半的代码只是透明地返回每个函数给出的确切值。

相比之下,使用新构造,可以按如下方式编写相同的代码

commit_write(OpaqueData) ->
    maybe
        ok ?= disk_log:sync(OpaqueData#backup.file_desc),
        ok ?= disk_log:close(OpaqueData#backup.file_desc),
        ok ?= file:rename(OpaqueData#backup.tmp_file, OpaqueData#backup.file),
        {ok, OpaqueData#backup.file}
    end.

或者,为了防止 disk_log 调用返回 ok | {error, Reason} 以外的内容,可以使用以下形式

commit_write(OpaqueData) ->
    maybe
        ok ?= disk_log:sync(OpaqueData#backup.file_desc),
        ok ?= disk_log:close(OpaqueData#backup.file_desc),
        ok ?= file:rename(OpaqueData#backup.tmp_file, OpaqueData#backup.file),
        {ok, OpaqueData#backup.file}
    else
        {error, Reason} -> {error, Reason}
    end.

这些调用的语义是相同的,只是现在更容易关注各个操作的流程以及成功或错误路径。

Dialyzer #

  • 当规范与推断类型不同时,Dialyzer 现在支持 missing_returnextra_return 选项以引发警告。这些类似于 overspecsunderspecs,但不如它们冗长。

  • Dialyzer 现在更好地理解了 min/2max/2erlang:raise/3 的类型。因此,Dialyzer 可能会生成新的警告。特别是,使用 erlang:raise/3 的函数现在可能需要一个具有 no_return() 返回类型的规范,以避免出现不必要的警告。

JIT 的改进 #

Erlang/OTP 24 中引入的 JIT 编译器 提高了 Erlang 应用程序的性能。

Erlang/OTP 25 引入了 JIT 的一些重大改进

  • JIT 现在支持 AArch64 (ARM64) 架构,该架构由(例如)Apple Silicon Mac 和较新的 Raspberry Pi 设备使用。

  • 基于 Erlang 编译器提供的类型生成更好的代码。

  • 更好地支持 perfgdb,其中包含 Erlang 代码的行号。

支持 AArch64 (ARM64) #

与解释器相比,从 JIT 可以期望获得的速度提升幅度从零到最多四倍不等。

为了获得更具体的数字,我们在一台 MacBook Pro(M1 处理器;2020 年发布)上禁用和启用 JIT 运行了三个不同的基准测试。

首先,我们运行了 EStone 基准测试。在没有 JIT 的情况下,实现了 691,962 个 EStone,而在使用 JIT 的情况下,实现了 1,597,949 个 EStone。也就是说,使用 JIT 的 EStone 数量是原来的两倍多。

接下来,我们尝试运行 Dialyzer 来构建一个小的 PLT

dialyzer --build_plt --apps erts kernel stdlib

使用 JIT,构建 PLT 的时间从 18.38 秒减少到 9.64 秒。也就是说,几乎但没有完全快一倍。

最后,我们为 base64 模块运行了一个基准测试,该模块包含在 此 Github 问题 中。

使用 JIT

== Testing with 1 MB ==
fun base64:encode/1: 1000 iterations in 11846 ms: 84 it/sec
fun base64:decode/1: 1000 iterations in 14617 ms: 68 it/sec

不使用 JIT

== Testing with 1 MB ==
fun base64:encode/1: 1000 iterations in 25938 ms: 38 it/sec
fun base64:decode/1: 1000 iterations in 20603 ms: 48 it/sec

使用 JIT 的编码速度几乎快了两倍半,而使用 JIT 的解码时间约为不使用 JIT 的解码时间的 75%。

基于类型的优化 #

JIT 一次将一个 BEAM 指令转换为本机代码,而不知道之前的任何指令。例如,+ 运算符的本机代码必须适用于任何操作数:适合 64 位字的 小整数、大整数、浮点数以及应导致引发异常的非数字。

在 Erlang/OTP 25 中,编译器将类型信息嵌入到 BEAM 文件中,以帮助 JIT 生成更好的本机代码,而无需不必要的类型测试。

有关更多详细信息,请参阅博客文章 JIT 中基于类型的优化

更好地支持 perfgdb #

现在可以使用 perf 对 Erlang 系统进行性能分析,并获得从 JIT 代码到相应 Erlang 代码的映射。这将使得查找代码中的瓶颈变得容易。

gdb 也是如此,它也可以显示 JIT 代码中的特定地址对应的 Erlang 代码行。

Perf 是一个用于轻量级 CPU 性能分析的 Linux 命令行工具;它检查 CPU 性能计数器、跟踪点、uprobe 和 kprobe,监控程序事件,并创建报告。

perf 下运行的 Erlang 节点可以像这样启动

perf record --call-graph fp -- erl +JPperf true

然后可以像这样查看 perf 的结果

perf report

也可以像这样将 perf 附加到已运行的 Erlang 节点

# start Erlang at get the Pid
erl +JPperf true

节点的 pid 是 4711

然后你可以像这样将 perf 附加到该节点

sudo perf record --call-graph fp -p 4711

下面是一个示例,其中 perf 运行以分析 dialyzer 构建 PLT,如下所示

 ERL_FLAGS="+JPperf true +S 1" perf record --call-graph=fp \
   dialyzer --build_plt -Wunknown --apps compiler crypto erts kernel stdlib \
   syntax_tools asn1 edoc et ftp inets mnesia observer public_key \
   sasl runtime_tools snmp ssl tftp wx xmerl tools

上面的代码使用 +S 1 运行,以使 perf 输出更容易理解。如果然后运行 perf report -f --no-children,你可能会得到类似于以下的结果

alt text

当传递 +JPperf true 选项时,会启用帧指针,因此你可以使用 perf record --call-graph=fp 来获取更多上下文。

报告中的任何 Erlang 函数都以 $ 为前缀,所有 C 函数都具有其正常名称。任何带有前缀 $global:: 的 Erlang 函数都指的是全局共享片段。

因此,在上面的示例中,我们可以看到我们花费最多的时间在执行 eq,即比较两个项。通过展开它并查看其父项,我们可以看到是函数 erl_types:t_is_equal/2 对此值贡献最大。去看看源代码,看看是否能找出为什么在这里花费了这么多时间。

eq 之后,我们看到函数 erl_types:t_has_var/1,我们在其中花费了整个执行时间的近 5%。再往下看,你可以看到 copy_struct_x,它是用于复制项的函数。如果我们展开它以查看父项,我们会发现主要是 ets:lookup_element/3 通过 Erlang 函数 dialyzer_plt:ets_table_lookup/2 对此时间做出贡献。

perf 的提示和技巧 #

你可以使用 perf 做很多很棒的事情。下面列出了一些我们发现有用的选项

  • perf report --no-children 不要包括调用中所有子项的累积。
  • perf report --call-graph callee 展开函数调用时显示被调用者而不是调用者。
  • perf archive 创建一个包含在另一主机上检查数据所需的所有工件的存档。在早期版本的 perf 中,此命令不起作用,你可以使用 此 bash 脚本
  • perf report 给出“failed to process sample”和/或“failed to process type: 68” 这可能意味着你正在运行一个有 bug 的 perf 版本。当运行内核版本为 4 的 Ubuntu 18.04 时,我们遇到了这种情况。如果你更新到 Ubuntu 20.04 或使用内核版本为 5 的 Ubuntu 18.04,该问题应该会消失。

改进了二进制构造失败时的错误信息 #

Erlang/OTP 24 引入了改进的 BIF 错误信息,以便在对 BIF 的调用失败时提供更多信息。

在 Erlang/OTP 25 中,当使用位语法创建二进制文件失败时,也会提供改进的错误信息。

考虑这个函数

bin(A, B, C, D) ->
    <<A/float,B:4/binary,C:16,D/binary>>.

如果在过去的版本中使用不正确的参数调用此函数,我们只会得到一些错误和行号的提示

1> t:bin(<<"abc">>, 2.0, 42, <<1:7>>).
** exception error: bad argument
     in function  t:bin/4 (t.erl, line 5)

但是,第 5 行的哪一部分?假设 t:bin/4 是从应用程序深处调用的,并且我们不知道参数的实际值。可能需要一段时间才能弄清楚到底发生了什么错误。

Erlang/OTP 25 为我们提供了更多信息

1> c(t).
{ok,t}
2> t:bin(<<"abc">>, 2.0, 42, <<1:7>>).
** exception error: construction of binary failed
     in function  t:bin/4 (t.erl, line 5)
        *** segment 1 of type 'float': expected a float or an integer but got: <<"abc">>

请注意,该模块必须由 Erlang/OTP 25 中的编译器编译,才能获得更详细的错误消息。如果该模块是由以前的版本编译的,则会显示旧样式的消息。

这里的消息告诉我们,构造中的第一个段被赋予了二进制 <<"abc">>,而不是浮点数或整数,而浮点数是 float 段的预期类型。

看起来我们交换了 bin/4 的第一个和第二个参数,所以我们再试一次

3> t:bin(2.0, <<"abc">>, 42, <<1:7>>).
** exception error: construction of binary failed
     in function  t:bin/4 (t.erl, line 5)
        *** segment 2 of type 'binary': the value <<"abc">> is shorter than the size of the segment

看起来不止一个参数不正确。在这种情况下,消息告诉我们给定的二进制文件比段的大小短。

修复该问题

4> t:bin(2.0, <<"abcd">>, 42, <<1:7>>).
** exception error: construction of binary failed
     in function  t:bin/4 (t.erl, line 5)
        *** segment 4 of type 'binary': the size of the value <<1:7>> is not a multiple of the unit for the segment

binary 段的默认单位为 8。因此,传递大小为 7 的位字符串将失败。

最后

5> t:bin(2.0, <<"abcd">>, 42, <<1:8>>).
<<64,0,0,0,0,0,0,0,97,98,99,100,0,42,1>>

改进了记录匹配失败时的错误信息 #

另一个改进是记录匹配失败时的异常。

考虑这个记录和函数

-record(rec, {count}).

rec_add(R) ->
    R#rec{count = R#rec.count + 1}.

在过去的版本中,未能匹配记录或从记录中检索元素将导致以下异常

1> t:rec_add({wrong,0}).
** exception error: {badrecord,rec}
     in function  t:rec_add/1 (t.erl, line 8)

在 Erlang/OTP 15 中引入异常中的行号之前,如果错误发生在大型函数中,了解预期的记录可能很有用。

如今,除非在同一行访问多个不同的记录,否则行号会清楚地表明预期的是哪个记录。

因此,在 Erlang/OTP 25 中,badrecord 异常已更改为显示实际的不正确值

2> t:rec_add({wrong,0}).
** exception error: {badrecord,{wrong,0}}
     in function  t:rec_add/1 (t.erl, line 8)

对于使用 Erlang/OTP 25 编译的代码,将显示新的 badrecord 异常。

可重定位的安装目录 #

以前,Erlang 安装的 shell 脚本(例如,erlstart)和 RELEASES 文件依赖于安装根目录的硬编码绝对路径。这使得将安装移动到不同的目录变得麻烦,这对于诸如 Android (#2879) 之类的平台可能会有问题,在这些平台上,安装目录在编译时是未知的。这已通过以下方式修复:

  • 更改 shell 脚本,使其可以动态查找 ROOTDIR。如果动态找到的 ROOTDIR 与硬编码的 ROOTDIR 不同,并且看起来指向有效的 Erlang 安装,则会选择它。已更改 dyn_erl 程序,使其在给定 –realpath 参数时可以返回其绝对规范化路径(dyn_erl 从 realpath POSIX 函数获取其绝对规范化路径)。脚本使用 dyn_erl 的 –realpath 功能来动态获取根目录。

  • 更改读取和写入 RELEASES 文件的 release_handler 模块,使其在遇到相对路径时预先添加 code:root_dir()。这是必要的,因为当前工作目录可能会更改,因此它与 code:root_dir() 不同。

具有自适应写入并发支持的 ETS 表 #

长期以来,可以使用以下方式优化 ETS 表以实现写入并发

ets:new(my_table, [{write_concurrency, true}]).

现在,我们还引入了对写入并发的自适应支持,可以像这样配置

ets:new(my_table, [{write_concurrency, auto}]).

此选项强制表根据检测到的并发量在运行时自动更改使用的锁的数量。当你启用自动写入并发时,还会激活 decentralized_counters,以实现更可扩展的 ETS 表。当你知道很多进程将在具有多个内核的系统上访问 ETS 表时,请使用此选项。

有关更多详细信息,你可以阅读引入更改的 PR 5208关于分散式计数器的博客文章

erlang:float_to_list/2erlang:float_to_binary/2 的新选项 short #

一个名为 short 的新选项已添加到函数 erlang:float_to_list/2erlang:float_to_binary/2 中。此选项创建给定浮点数的最短的、正确舍入的字符串表示形式,该字符串可以再次转换回相同的浮点数。

如果指定了选项 short,则使用最小的位数格式化浮点数,该位数仍能保证

F =:= list_to_float(float_to_list(F, [short]))

当浮点数在范围 (-2⁵³, 2⁵³) 内时,使用产生最少字符数的表示法(科学计数法或普通十进制表示法)。范围 (-2⁵³, 2⁵³) 之外的浮点数始终使用科学计数法格式化,以避免在进行算术运算时产生令人困惑的结果。

该实现由 Thomas Depierre 贡献,并使用了 Ryū 算法。

Ryū 是一种新的算法,它仅使用固定大小的整数运算将二进制浮点数转换为其十进制表示形式。Ryū 比以前最快的实现更简单,速度大约快三倍。https://github.com/ulfjack/ryu

新模块 peer 取代了 slave 模块 #

peer 模块提供了用于启动链接的 Erlang 节点的函数。生成新“peer”节点的 Erlang 节点称为 origin,新启动的节点是 peers。

当 peer 节点失去与 origin 的控制连接时,它会自动终止。此连接可以是 Erlang 分布连接,也可以是替代连接 - TCP 或标准 I/O。即使 Erlang 分布不可用,替代连接也提供了一种执行远程过程调用的方法,从而可以测试分布本身。

peer 节点的终端输入/输出通过 origin 中继。如果请求标准 I/O 替代连接,控制台输出也会通过 origin,从而可以调试节点启动和引导脚本执行(请参阅 -init_debug)。与 slave 行为相反,文件 I/O 不会被重定向。

peer 节点可以在相同或不同的主机(通过 ssh)或单独的容器(例如 Docker)中启动。当 peer 在与 origin 相同的主机上启动时,它会从 origin 继承当前目录和环境变量。

注意 #

此模块旨在促进使用 Common Test 进行多节点测试。使用 ?CT_PEER() 宏根据 Common Test 约定启动链接的 peer 节点:崩溃转储写入特定位置,节点名称以模块名称、调用函数和 origin OS 进程 ID 为前缀)。如果你需要更多控制,请使用 random_name/1 创建足够唯一的节点名称。

在没有替代连接的情况下启动的 peer 节点的行为类似于 slave(3)

gen_XXX 模块有一个新的 format_status/1 回调。 #

gen_servergen_statemgen_eventformat_status/2 回调函数已被弃用,取而代之的是新的 format_status/1 回调函数。

新的回调函数增加了限制和更改更多内容的可能性,而不仅仅是状态。

新旧 format_status 回调函数的目的都是让用户从崩溃报告中过滤掉敏感信息,以及可能的大量数据。

timer 模块已进行现代化改造,效率更高 #

timer 模块已进行现代化改造,效率更高,这使得 timer 服务器不太容易过载。现在,timer:sleep/1 函数接受任意大的整数。

加密和 OpenSSL 3.0 #

OTP 中的一些应用程序(如 SSL/TLS 和 SSH)需要加密才能工作。这由 OTP 应用程序 crypto 提供,它使用 NIF 将 Erlang 连接到 C 中的外部加密库。这种外部加密库的主要示例是 OpenSSL

OpenSSL 加密库存在许多版本。OTP/crypto 支持 0.9.8c 及更高版本,尽管 OpenSSL 仅维护 1.1.1 版本。

OpenSSL 发布了其 3.0 系列版本,这是他们完全使用新的 API 重建的未来平台。以前版本(1.1.1 和更早版本)的 API 已部分弃用,尽管在 3.0 中仍然可用。对 1.1.1 的支持也将在未来结束。

由于在加密库中获取安全补丁至关重要,并且未来可能只有 3.0 API 可用,因此从 OTP-25.0 开始,OTP/crypto 现在使用新的 3.0 API 连接 OpenSSL 3.0。仍然使用了旧 API 中的一些函数,但它们将尽快被替换。

您作为用户应该不会注意到任何差异:如果您有 OpenSSL 1.1.1(或更早的版本 - 不推荐)并构建 OTP,则将像以前一样使用该版本。如果您安装了任何 OpenSSL 3.0 版本,则将使用该版本,而无需执行任何特殊操作,只需在操作系统中正常处理动态加载路径即可。

可以从操作系统的标准位置获取 CA 证书 #

使用新函数 public_key:cacerts_load/0,1public_key:cacerts_get/0,可以从操作系统的标准位置(或从文件中)获取 CA 证书。

然后,它们将通过使用 persistent_term 以解码形式缓存,这使得它们可以有效地用于 sslhttpc 模块。这样做的目的是使其在许多软件包中不必依赖例如 certifi

在 Windows 和 MacOSx 上,证书存储不是普通文件,因此信息是通过使用 NIF(Windows)或使用外部程序(MacOSx)的 API 获取的。

ssl 示例

%% makes the certificates available without copying
CaCerts = public_key:cacerts_get(), 
% use the certificates when establishing a connection
{ok,Socket} = ssl:connect("erlang.org",443,[{cacerts,CaCerts}, {verify,verify_peer}]), 
...

我们还计划尽快更新 http 客户端 (httpc) 以使用此功能。

一个新的快速伪随机数生成器 #

实现了一个新的自定义设计的伪随机数生成器 rand:mwc59。它可能是可以使用 Erlang 编写的最快、质量良好的生成器。为此,它几乎避免了大数、分配堆数据,并且仅使用最少数量的快速操作。

在“正确”的情况下:使用默认生成器需要 60 纳秒才能生成的数字,可以使用 rand:mwc59 在 4 纳秒内生成。

它适用于急需 PRNG 数字速度的应用程序,但不适用于 rand 提供的任何舒适功能。