R12B中引入了bit string(bits),其包含任意数目的bit,如果其包含的bit数目可以被8整除,那么我们称其为binary(bytes)。
使用bits
有了bits,我们对某些协议的操作更加简便灵活。
比如IS 683-PRL 协议,其首部包含5个bit指示后面有多少个连续的11 bit的数据。
在以前我们要解析其这个数据包了非常繁琐,请看如下:
decode(<<NumChans:5, _Pad:3, _Rest/binary>> = Bin) ->
decode(Bin, NumChans, NumChans, []).
decode(_, _, 0, Acc) ->
Acc;
decode(Bin, NumChans, N, Acc) ->
SkipBef = N * 11,
SkipAft = (NumChans - N) * 11,
Pad = 8 - ((NumChans * 11 + 5) rem 8),
<<_:5, _:SkipBef, V:11, _:SkipAft, _:Pad>> = Bin,
decode(Bin, NumChans, N-1, [V | Acc]).
是不是很复杂,之所以如此负载,就是因为原来binary拥有数据的最小单位是byte,所以我们只能用很多手法来定位解析我们要的数据。
而现在有了bits,我们可以非常轻松的实现上面的代码:
第一种方法:
decode(<<NumChans:5, Rest/binary>>) ->
decode(Rest, NumChans, []).
decode(<<V:11, Rest/bits>>, N, Acc) ->
decode(Rest, N-1, [V | Acc]);
decode(_, 0, Acc) ->
lists:reverse(Acc).
是不是简洁很多?
我们还有第二种方法:
decode(<<NumChans:5, Chans:NumChans/bits-unit:11, _/bits>>) ->
[Chan || <<Chan:11>> <= Chans].
binaries的创建
erlang在创建一个binary bin1的时候,会预留一定的空间(整个binary大小为:实际占用空间的2倍或256两者中较大者),这样当我们连续的在尾部添加bin2时,只需要将bin2追加到原binary的预留空间即可,不用重新分配空间,也不用拷贝原有bin1的数据。因此效率很高。
比如
Bits拥有1000个bit的数据,同时拥有600个bit的未使用空间,下面的表达式:
NewBits = <<Bits/bits, 12:32>>,
NewBits只是指向了原有的Bits,同时将32bit的数据追加到Bits中,此时NewBits拥有1032个bit的数据,568个未使用bit。而原有的Bits,此时还是保持原有的数据。
接着你写下了下面的语句:
NewBits2 = <<Bits/bits, 12:64>>,
如果我们还在Bits后面追加,那么显然NewBits2不是我们想要的结果。这里没有优化的可能了。
只能老老实实的创建一个新的binary,然后将Bits1的内容拷贝过来。
binary匹配
如何写出能够binary匹配优化的代码?
1, 牢牢记住下面的代码框架
f(<<Pattern1,...,Rest/bits>>,...) ->
... % Rest is not used here
f(Rest,...);
f(<<Pattern2,...,Rest/bits>>,...) ->
... % Rest is not used here
f(Rest,...);
...
f(<<>>, ...) ->
ReturnValue
即不要在binary匹配的clause中返回或使用Rest binary,仅仅是将其作为参数传递给下一个自身函数调用。
2,函数中的clause如果是进行binary匹配,那么这些进行匹配的clause需要连续。(后面可以看到一个例子)
这样我们的代码才会被最大程度的binary优化。在这样的函数中,直到函数返回,我们只创建了一个match context,没有创建任何其他的sub binary。
要点就是Rest在代码中不要做任何其他使用,而是直接交给递归函数。
注:match contex和sub binary都是binary的一种,binary在内部实现中共有四种类型,另外两种类型是Refc binaries和
Heap binaries。具体的参看http://www.erlang.org/doc/efficiency_guide /binaryhandling.html#4。需要说明的是match context只在binaries match的时候生成,其比sub binary更加高效。
如何查看我的代码是否执行了binary 优化?
只要加上bin_opt_info选项即可,可以添加在命令行,也可以添加在源文件的-compile属性中:
erlc + bin_opt_info my.erl
或
%% my.erl
-module(my).
-compile([bin_opt_info]).
binary匹配代码优化举例
通过bin_opt_info编译选项,我们看几个例子,我们把编译器的提示信息作为注释加入到代码中,方便您查看未优化原因。
1, 未优化,因为函数参数的顺序,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.
优化:
opt_eq(<<H,T1/binary>>, [H|T2]) ->
opt_eq(T1, T2);
opt_eq(<<_,_/binary>>, [_|_]) ->
false;
opt_eq(<<>>, []) ->
true.
2,未优化,不合理的匹配语法,导致无法优化,这里的Bad模式匹配,影响优化。
not_opt_sum1(<<A, Rest/binary>> = Bad, Acc) ->
%Warning: NOT OPTIMIZED: called function not_opt_sum1/2
%does not begin with a suitable binary matching instruction
not_opt_sum1(Rest, A + Acc);
not_opt_sum1(<<>>, Acc) ->
Acc.
优化:
去掉Bad
3,未优化,sub binary被使用。毫无疑问这里的bit_size使用了Rest.
not_opt_sum2(<<A, Rest/binary>>, Acc) ->
bit_size(Rest), %Warning: NOT OPTIMIZED: sub binary is used or returned
not_opt_sum2(Rest, A + Acc);
not_opt_sum2(<<>>, Acc) ->
Acc.
优化:
去除bit_size(Rest)表达式
4,未优化,sub binary被返回,即第一个clause中 T被返回。
not_opt_zero(<<0, T/binary>>) ->
T; %Warning: NOT OPTIMIZED: sub binary is used or returned
not_opt_zero(<<A, T/binary>>) ->
not_opt_zero(T).
优化:
opt_zero(<<0, T/binary>>, not_found) ->
opt_zero(T, found);
opt_zero(<<A, T/binary>>, _) ->
opt_zero(T, not_found);
opt_zero(T, found) ->
T.
5,未优化,多个binary匹配clause不连续导致(这个例子是从avindev的blog偷来的)
extract_str_end_with_tag(Data, Tag) ->
extract_str_end_with_tag2(Data, <<>>, Tag, not_found).
extract_str_end_with_tag2(<<Tag, T/binary>>, Buffer, Tag, _) ->
extract_str_end_with_tag2(T, Buffer, Tag, found);
extract_str_end_with_tag2(<<B, T/binary>>, Buffer, Tag, not_found) ->
extract_str_end_with_tag2(T, <<Buffer/binary, B>>, Tag, not_found);
extract_str_end_with_tag2(Rest, Buffer, _, found) ->
{found, Buffer, size(Buffer), Rest};
% Warning: INFO: non-consecutive clauses that
%match binaries will prevent delayed sub binary optimization
extract_str_end_with_tag2(<<>>, Buffer, _Tag, _) ->
{not_found, Buffer}.
优化:
extract_str_end_with_tag(Data, Tag) ->
extract_str_end_with_tag2(Data, <<>>, Tag, not_found).
extract_str_end_with_tag2(<<Tag, T/binary>>, Buffer, Tag, _) ->
extract_str_end_with_tag2(T, Buffer, Tag, found);
extract_str_end_with_tag2(<<B, T/binary>>, Buffer, Tag, not_found) ->
extract_str_end_with_tag2(T, <<Buffer/binary, B>>, Tag, not_found);
extract_str_end_with_tag2(<<>>, Buffer, _Tag, _) ->
{not_found, Buffer}.
extract_str_end_with_tag2(Rest, Buffer, _, found) ->
{found, Buffer, size(Buffer), Rest};
其实在写代码过程中,优化不是一个开始就要做的环节。首先正确实现工程最重要。但是如果我们了解一些优化的方法,写代码时顺手牵羊让代码
更加高效,何乐不为呢?
另外也不可把bin_opt_info的提示作为圣旨,一味追求OPTIMIZED,而扭曲了本来程序的逻辑。
备注:
遇到两个问题:
1:decode(<<NumChans:5, Chans:NumChans/bits-unit:11, _/bits>>) ->
[Chan || <<Chan:11>> <= Chans].
在R12B-3无法编译通过
2;avindev的blog中提出关于binary优化的一个bug? http://avindev.iteye.com/blog/208927
参考:
http://www.erlang.org/doc/efficiency_guide/binaryhandling.html#4
http://www.erlang.se/euc/07/papers/1700Gustafsson.pdf
另外很高兴认识mryufeng,给予我很大帮助!
Update:
1, decode(<<NumChans:5, Chans:NumChans/bits-unit:11, _/bits>>) ->
[Chan || <<Chan:11>> <= Chans].
语法错误! 是理解binary错误, 正确写法:
decode(<<NumChans:5, Chans:NumChans/binary-unit:11, _/bits>>) ->
[Chan || <<Chan:11>> <= Chans].
因为bits(bitstring)默认的unit只能为1, 我们这里指定11,所以编译提示:bit type mismatch (unit) between 11 and 8, 我们将bits改为binary既可.
这里请注意:bits(bitstring)的unit为1, bytes的unit为8, 而binary的unit为1-256