能够经过如下方式有效地构建二进制:ide
my_list_to_binary(List) ->函数
my_list_to_binary(List, <<>>).优化
my_list_to_binary([H|T], Acc) ->ui
my_list_to_binary(T, <<Acc/binary,H>>);spa
my_list_to_binary([], Acc) ->指针
Acc.对象
二进制能够像这样有效地匹配:进程
my_binary_to_list(<<H,T/binary>>) ->ci
[H|my_binary_to_list(T)];编译器
my_binary_to_list(<<>>) -> [].
在内部,二进制和位串以相同的方式实现。在本节中,它们被称为二进制,由于这就是它们在模拟器源代码中的名称。
内部有四种类型的二进制对象:
Refc二进制包含两个部分:
二进制对象可由任意数量的进程中的任意数量的ProcBins引用。该对象包含一个引用计数器,以跟踪引用的数量,以便在最后一个引用消失时能够将其删除。
进程中的全部ProcBin对象都是连接列表的一部分,所以,当ProcBin消失时,垃圾收集器能够跟踪它们并减小二进制中的引用计数器。
堆二进制是小型二进制,最多64个字节,并直接存储在进程堆中。当进程被垃圾回收而且做为消息发送时,它们被复制。它们不须要垃圾收集器进行任何特殊处理。
引用对象子二进制和匹配上下文能够引用refc二进制或堆二进制的一部分。
子二进制经过建立split_binary/2,而且当二进制以二进制模式匹配的。子二进制是对另外一个二进制(refc或堆二进制,而不是对另外一个子二进制)的一部分的引用。所以,匹配二进制相对便宜,由于从不复制实际的二进制数据。
匹配上下文相似于子二进制,但对于二进制匹配被优化。例如,它包含一个指向二进制数据的直接指针。对于从二进制中匹配的每一个字段,匹配上下文中的位置会增长。
编译器试图避免生成用于建立子二进制的代码,而只是在不久以后建立一个新的匹配上下文并丢弃该子二进制。保留匹配上下文,而不是建立子二进制。
若是编译器知道不会共享匹配上下文,则只能进行此优化。若是将其共享,则Erlang的功能属性(也称为参照透明性)将中断。
运行时系统特别优化了附加到二进制或位串的操做:
<<Binary/binary, ...>>
<<Binary/bitstring, ...>>
当运行时系统处理优化(而不是编译器)时,在极少数状况下优化不起做用。
为了解释它是如何工做的,让咱们逐行检查如下代码:
Bin0 = <<0>>, %% 1
Bin1 = <<Bin0/binary,1,2,3>>, %% 2
Bin2 = <<Bin1/binary,4,5,6>>, %% 3
Bin3 = <<Bin2/binary,7,8,9>>, %% 4
Bin4 = <<Bin1/binary,17>>, %% 5 !!!
{Bin4,Bin3} %% 6
运行时系统会发现Bin1是上一个追加操做(不是最新的追加操做)的结果,所以它将Bin1的内容复制到新的二进制中,保留了额外的存储空间,依此类推。(这里没有解释运行时系统如何知道不容许将其写入Bin1;好奇的读者能够将其做为练习,经过读取仿真器源代码(主要是erl_bits.c)来了解如何完成此操做。)
二进制追加操做的优化要求,有一个单一ProcBin和一个单一的引用到ProcBin用于二进制。缘由是能够在追加操做期间移动(从新分配)二进制对象,而且在这种状况下,必须更新ProcBin中的指针。若是将有多个ProcBin指向二进制对象,则将不可能找到并更新全部它们。
所以,对二进制的某些操做会对其进行标记,以便未来任何附加操做都将被强制复制二进制。在大多数状况下,二进制对象将同时缩小以回收分配给增加的额外空间。
当按以下所示追加到二进制时,仅从最新的追加操做返回的二进制将支持进一步的廉价追加操做:
Bin = <<Bin0,...>>
在本节开头的代码片断中,追加到Bin将很便宜,而追加到Bin0将强制建立新的二进制并复制Bin0的内容。
若是将二进制做为消息发送到进程或端口,则该二进制将缩小,而且任何进一步的追加操做会将二进制数据复制到新的二进制中。例如,在下面的代码片断中,Bin1将被复制到第三行:
Bin1 = <<Bin0,...>>,
PortOrPid!Bin1
Bin = <<Bin1,...>> %% Bin1将被复制
若是将二进制插入到Ets表中,或者使用erlang:port_command/2将其发送到端口,或者将其传递给NIF中的enif_inspect_binary,也会发生一样的状况。
匹配二进制也将致使其缩小,而且下一个追加操做将复制二进制数据:
Bin1 = <<Bin0,...>>,
<< X,Y,Z,T/binary>> = Bin1,
Bin = <<Bin1,...>> %% Bin1将被复制
缘由是匹配上下文包含指向二进制数据的直接指针。
若是进程仅保留二进制(在“循环数据”中或在进程字典中),则垃圾收集器最终能够收缩二进制。若是只保留一个这样的二进制,它将不会缩小。若是该过程稍后追加到已缩小的二进制中,则将从新分配二进制对象以放置要附加的数据。
让咱们回顾上一节开头的示例:
my_binary_to_list (<< H,T/binary >>) ->
[H | my_binary_to_list(T)];
my_binary_to_list (<< >>) -> []。
首次调用my_binary_to_list/1时,将建立一个匹配上下文。匹配上下文指向二进制的第一个字节。1个字节被匹配,而且匹配上下文被更新以指向二进制中的第二个字节。
在这一点上,建立一个子二进制是有意义的,可是在此特定示例中,编译器发现很快将调用一个函数(在本例中为my_binary_to_list/1自己),该函数将当即建立一个新的匹配上下文并丢弃子二进制。
所以,my_binary_to_list/1会使用match上下文而不是子二进制进行调用。初始化匹配操做的指令在看到已传递给匹配上下文而不是二进制时,基本上什么也不作。
当到达二进制的末尾而且第二个子句匹配时,匹配上下文将被简单地丢弃(在下一个垃圾回收中将其删除,由于再也不有对其的引用)。
总而言之,my_binary_to_list/1仅须要建立一个匹配上下文,而无需子二进制。
请注意,遍历整个二进制后,将放弃my_binary_to_list/1中的match上下文。若是迭代在到达二进制末尾以前中止,会发生什么状况?优化是否仍然有效?
after_zero(<<0,T/binary>>) ->
T;
after_zero(<<_,T/binary>>) ->
after_zero(T);
after_zero(<<>>) ->
<<>>.
是的,它会的。编译器将在第二个子句中删除子二进制的构建:
...
after_zero(<<_,T/binary>>) ->
after_zero(T);
...
可是它将生成在第一个子句中构建子二进制的代码:
after_zero(<<0,T/binary>>) ->
T;
...
所以,after_zero/1构建一个匹配上下文和一个子二进制(假定传递了一个包含零字节的二进制)。
以下代码也将获得优化:
all_but_zeroes_to_list(Buffer, Acc, 0) ->
{lists:reverse(Acc),Buffer};
all_but_zeroes_to_list(<<0,T/binary>>, Acc, Remaining) ->
all_but_zeroes_to_list(T, Acc, Remaining-1);
all_but_zeroes_to_list(<<Byte,T/binary>>, Acc, Remaining) ->
all_but_zeroes_to_list(T, [Byte|Acc], Remaining-1).
编译器在第二和第三子句中删除了子二进制的构建,并向第一子句添加了一条指令,该指令将Buffer从匹配上下文转换为子二进制(若是Buffer已是二进制,则不执行任何操做)。
可是在更复杂的代码中,如何知道是否应用了优化呢?
使用bin_opt_info选项可以使编译器打印许多有关二进制优化的信息。能够将其提供给编译器或erlc:
erlc +bin_opt_info Mod.erl
或经过环境变量传递:
export ERL_COMPILER_OPTIONS=bin_opt_info
注意,bin_opt_info并非要添加到Makefile的永久选项,由于它生成的全部消息都没法消除。所以,在大多数状况下,将选项传递给环境是最实用的方法。
警告以下:
./efficiency_guide.erl:60:警告:未优化:该函数返回了二进制
./efficiency_guide.erl:62:警告:已优化:匹配上下文已重用
为了更清楚地说明警告所指的代码,例如,如下示例中的警告以注释的形式插入它们所引用的子句以后,例如:
after_zero (<< 0,T/binary>>) -> %% BINARY CREATED:从函数返回二进制
T;
after_zero (<< _,T/binary >>) -> %%优化:重用匹配上下文
after_zero(T);
after_zero (<< >>) ->
<< >>。
第一个子句的警告说,不能延迟子二进制的建立,由于它将被返回。第二个子句的警告说将不会建立子二进制。
编译器会肯定变量是否未使用。为如下每一个功能生成相同的代码:
count1(<<_,T/binary>>, Count) -> count1(T, Count+1);
count1(<<>>, Count) -> Count.
count2(<<H,T/binary>>, Count) -> count2(T, Count+1);
count2(<<>>, Count) -> Count.
count3(<<_H,T/binary>>, Count) -> count3(T, Count+1);
count3(<<>>, Count) -> Count.
在每次迭代中,二进制中的前8位将被跳过,不匹配。
R12B中的二进制处理获得了显着改善。因为在R11B中有效的代码在R12B中可能无效,反之亦然,所以本《效率指南》的较早版本包含一些有关R11B中二进制处理的信息。