zoukankan      html  css  js  c++  java
  • 【译】构造和匹配二进制(Efficiency Guide)

    可以通过以下方式有效地构建二进制:

    my_list_to_binary(List) ->

        my_list_to_binary(List, <<>>).

    my_list_to_binary([H|T], Acc) ->

        my_list_to_binary(T, <<Acc/binary,H>>);

    my_list_to_binary([], Acc) ->

        Acc.

    二进制可以像这样有效地匹配:

    my_binary_to_list(<<H,T/binary>>) ->

        [H|my_binary_to_list(T)];

    my_binary_to_list(<<>>) -> [].

    4.1如何实现二进制

    在内部,二进制和位串以相同的方式实现。在本节中,它们被称为二进制,因为这就是它们在模拟器源代码中的名称。

    内部有四种类型的二进制对象:

    • 两个是二进制数据的容器,它们被称为:
      • Refc Binaries引用计数二进制的缩写)
      • 堆二进制
    • 两个仅仅是对二进制一部分的引用,它们被称为:
      • 子二进制
      • 匹配上下文

    Refc二进制

    Refc二进制包含两个部分:

    • 存储在进程堆上的对象,称为ProcBin
    • 二进制对象本身,存储在所有进程堆的外部

    二进制对象可由任意数量的进程中的任意数量的ProcBins引用。该对象包含一个引用计数器,以跟踪引用的数量,以便在最后一个引用消失时可以将其删除。

    进程中的所有ProcBin对象都是链接列表的一部分,因此,当ProcBin消失时,垃圾收集器可以跟踪它们并减少二进制中的引用计数器。

    堆二进制

    堆二进制是小型二进制,最多64个字节,并直接存储在进程堆中。当进程被垃圾回收并且作为消息发送时,它们被复制。它们不需要垃圾收集器进行任何特殊处理。

    子二进制

    引用对象子二进制匹配上下文可以引用refc二进制或堆二进制的一部分。

    子二进制通过创建split_binary/2,并且当二进制以二进制模式匹配的。子二进制是对另一个二进制(refc或堆二进制,而不是对另一个子二进制)的一部分的引用。因此,匹配二进制相对便宜,因为从不复制实际的二进制数据。

    匹配上下文

    匹配上下文类似于子二进制,但对于二进制匹配被优化。例如,它包含一个指向二进制数据的直接指针。对于从二进制中匹配的每个字段,匹配上下文中的位置会增加。

    编译器试图避免生成用于创建子二进制的代码,而只是在不久之后创建一个新的匹配上下文并丢弃该子二进制。保留匹配上下文,而不是创建子二进制。

    如果编译器知道不会共享匹配上下文,则只能进行此优化。如果将其共享,则Erlang的功能属性(也称为参照透明性)将中断。

    4.2构造二进制

    运行时系统特别优化了附加到二进制或位串的操作:

    <<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

    • 1行(标有%% 1注释)将堆二进制分配给Bin0变量。
    • 2行是追加操作。由于Bin0尚未参与追加操作,新的REFC二进制被创建和内容Bin0被复制到它。refc二进制的ProcBin部分的大小设置为存储在二进制中的数据的大小,而二进制对象分配了额外的空间。二进制对象的大小是Bin1256的大小的两倍,以较大者为准。在这种情况下为256。
    • 3行更有趣。Bin1已经在附加操作中使用,并且它具有252个字节在末端未使用的存储空间,所以3个新字节被存储在那里。
    • 4行。此处同样适用。剩下249个字节,因此存储另外3个字节没有问题。
    • 5行。在这里,发生了一些有趣的事情。请注意,结果不追加到以前的结果Bin3,但Bin1。预期将为Bin4赋值<<0,1,2,3,17>>。还可以预期Bin3将保留其值(<<0,1,2,3,4,5,6,7,8,9>>)。显然,运行时系统无法将字节17写入二进制,因为这会将Bin3的值更改<<0,1,2,3,4,17,6,7,8,9>>

    运行时系统会发现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将被复制

    原因是匹配上下文包含指向二进制数据的直接指针。

    如果进程仅保留二进制(在“循环数据”中或在进程字典中),则垃圾收集器最终可以收缩二进制。如果只保留一个这样的二进制,它将不会缩小。如果该过程稍后追加到已缩小的二进制中,则将重新分配二进制对象以放置要附加的数据。

    4.3匹配二进制

    让我们回顾上一节开头的示例:

    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

    使用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位将被跳过,不匹配。

    4.4历史记录

    R12B中的二进制处理得到了显着改善。由于在R11B中有效的代码在R12B中可能无效,反之亦然,因此本《效率指南》的较早版本包含一些有关R11B中二进制处理的信息。

  • 相关阅读:
    PHP 方法整合类 -- 1.根据概率产生随机数 --2.判断手机号归属地及运营商 --3.过滤emoji表情
    PHP 多图下载并打包压缩方法
    PHP 导出excel 精简版
    PHP获取首字母相关方法
    no input file specified 解决办法
    百度地图相关
    经纬度相关方法
    阿里云SSL证书部署至宝塔
    微信入口、生成菜单,公众号授权获取用户信息(unionid)
    超级好用超级简单的支付类库
  • 原文地址:https://www.cnblogs.com/sunbin-hello/p/13099209.html
Copyright © 2011-2022 走看看