[Erlang0005][OTP] 高效指南 二进制的构造和匹配(2)

原文链接:http://www.erlang.org/doc/efficiency_guide/binaryhandling.html
(水平有限,错误之处欢迎指正)

4.3 匹配二进制

我们再来回顾一下之前的例子,看看到底发生了什么。

DO (in R12B)
my_binary_to_list(<<H,T/binary>>) ->
[H|my_binary_to_list(T)];
my_binary_to_list(<<>>) -> [].

my_binary_to_list/1第一次被调用时,会创建一个match context。match context指向二进制的第一个字节。第一个字节被匹配出来后它就自动指向第二个字节。


在R11B中,碰到这种情况时会创建一个sub binary。但在R12B中,这么做似乎意义不大,因为接下来就会有一个函数调用(上面的例子会调用自己,my_binary_to_list/1),创建一个新的match context,抛弃sub binary。


因此,在R12B中,my_binary_to_list/1会调用自己并传递一个match context,而不是传sub binary。如果传递的是match context,初始化匹配操作的指令基本上什么都不会做。


当上面例子里的二进制全部匹配完毕,匹配到第二个函数头时,match context就被丢弃了(被垃圾回收了,因为没有任何引用了)。


也就是说,在R12B中,my_binary_to_list/1只需要创建一个match context。而在R11B中,如果二进制有N个字节,就要创建N+1个match contexts和N个sub binaries。


在R11B中,最快的方法匹配二进制是:

my_complicated_binary_to_list(Bin) ->
    my_complicated_binary_to_list(Bin, 0).

my_complicated_binary_to_list(Bin, Skip) ->
    case Bin of
    <<_:Skip/binary,Byte,_/binary>> ->
        [Byte|my_complicated_binary_to_list(Bin, Skip+1)];
    <<_:Skip/binary>> ->
        []
    end.

  

这个函数巧妙的避过了创建sub binaries,但是不可避免在每次递归都创建match context。因此,不管在R11B还是R12B中,my_complicated_binary_to_list/1都创建了N+1个match context。(在未来的发布版本中,编译器或许能够生成代码来重用match context,但别期望太高。)


回到my_binary_to_list/1,记住,match context会在整个二进制被遍历后丢弃掉。如果在二进制结束之前停止迭代会怎样?优化还会有效么?

after_zero(<<0,T/binary>>) ->
    T;
after_zero(<<_,T/binary>>) ->
    after_zero(T);
after_zero(<<>>) ->
    <<>>.

 是的,依旧有效。编译器会在第二个子函数处移除sub binary的创建

.
.
.
after_zero(<<_,T/binary>>) ->
    after_zero(T);
.
.
.

 但会在第一个子函数那里生成创建sub binary的代码

after_zero(<<0,T/binary>>) ->
    T;
.
.
.

  

因此,after_zero/1会创建一个match context和一个sub binary(传递一个包含“0”的二进制)。


类似下面的代码也会被优化:

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

  

编译器会在函数的第二和第三子函数移除sub binaries的创建,同时在第一个子函数处会增加一个指令,把Buffer从match context转化成sub bianry(或者什么也不做,如果Buffer已经是一个二进制)。


在你感觉编译器会优化所有二进制模式之前,这里有一个函数编译器将不会去优化(目前,至少是):

non_opt_eq([H|T1], <<H,T2/binary>>) ->
    non_opt_eq(T1, T2);
non_opt_eq([_|_], <<_,_/binary>>) ->
    false;
non_opt_eq([], <<>>) ->
    true.

  

之前有提到过编译器只能延迟生成sub binaries,如果它能确定二进制不会被共享。上面这种情况,编译器不能确定。


我们很快会告诉你如何重写non_opt_eq/2,以便能够通过延迟sub binary来优化,更重要的是,我们将展示如何来确定你的代码是否会被优化。

bin_opt_info 参数

 使用bin_opt_info参数让编译器打印出关于二进制优化的信息。编译和erlc均有此功能

erlc +bin_opt_info Mod.erl

 或者传一个环境变量

export ERL_COMPILER_OPTIONS=bin_opt_info

 注意不要把bin_opt_info作为一个永久参数添加到你的MakefileS,因为消除这个参数所生成的所有信息是不可能的。因此,作为环境变量来传递这个参数在大多数情况下显得更为实际。


警告示例如下:

./efficiency_guide.erl:60: Warning: NOT OPTIMIZED: sub binary is used or returned
./efficiency_guide.erl:62: Warning: OPTIMIZED: creation of sub binary delayed

为了更清楚的显示哪里被警告了,下面的例子里,警告被替换成注释:

after_zero(<<0,T/binary>>) ->
         %% NOT OPTIMIZED: sub binary is used or returned
    T;
after_zero(<<_,T/binary>>) ->
         %% OPTIMIZED: creation of sub binary delayed
    after_zero(T);
after_zero(<<>>) ->
    <<>>.

 第一个子函数的警告告诉我们延迟创建sub binary是不可能的,以为它是返回值。第二个子函数的警告告诉我们sub binary将不会被创建。


现在让我们来看看之前那个不会被编译器优化的例子,并找出原因:

non_opt_eq([H|T1], <<H,T2/binary>>) ->
        %% INFO: matching anything else but a plain variable to
    %%    the left of binary pattern will prevent delayed 
    %%    sub binary optimization;
    %%    SUGGEST changing argument order
        %% NOT OPTIMIZED: called function non_opt_eq/2 does not
    %%    begin with a suitable binary matching instruction
    non_opt_eq(T1, T2);
non_opt_eq([_|_], <<_,_/binary>>) ->
    false;
non_opt_eq([], <<>>) ->
    true.

  

编译器抛出两个警告。INFO警告指的是non_opt_eq/2被调用时,任何调用它的函数都不能进行延迟sub binary的优化。这里有个建议,改变参数的顺序。第二个警告指向sub binary本身。


稍后我们会展示另一个例子,来更清晰的对比INFO和NOT OPTIMIZED警告的差别,但这之前我们先听从建议,调换参数的顺序:

opt_eq(<<H,T1/binary>>, [H|T2]) ->
        %% OPTIMIZED: creation of sub binary delayed
    opt_eq(T1, T2);
opt_eq(<<_,_/binary>>, [_|_]) ->
    false;
opt_eq(<<>>, []) ->
    true.

编译器给如下代码碎片一个警告:

match_body([0|_], <<H,_/binary>>) ->
        %% INFO: matching anything else but a plain variable to
    %%    the left of binary pattern will prevent delayed 
    %%    sub binary optimization;
    %%    SUGGEST changing argument order
    done;
.
.
.

  

这个警告意味着,如果调用match_body/2(从match_body/2的另一个子函数或者别的函数),延迟sub binary的优化是不可能的。所有二进制被匹配出来并在最后被作为match_body/2的第二个参数传递的地方都会添加这个警告:


match_head(List, <<_:10,Data/binary>>) ->

match_head(List, <<_:10,Data/binary>>) ->
        %% NOT OPTIMIZED: called function match_body/2 does not
    %%     begin with a suitable binary matching instruction
    match_body(List, Data).

  

不被使用的变量 

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位被跳过,没有被匹配出来。

(原创翻译,欢迎任何形式的转载,但请务必注明出处:http://www.cnblogs.com/liangjingyang)
原文地址:https://www.cnblogs.com/liangjingyang/p/2591951.html