原文链接: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)