• [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)
  • 相关阅读:
    Java设计模式-装饰器模式
    【c++内存分布系列】单独一个类
    【转】LCS
    快速排序
    冒泡排序
    选择排序
    多线程读取全局变量
    【转】一致性hash算法(consistent hashing)
    【转】五笔的字典序编码与解码
    给定一个函数rand()能产生0到n-1之间的等概率随机数,问如何产生0到m-1之间等概率的随机数?
  • 原文地址:https://www.cnblogs.com/liangjingyang/p/2591951.html
Copyright © 2020-2023  润新知