实例分析Erlang二进制(Binary)的匹配优化

从《实例分析Erlang二进制(Binary)匹配的内部机制》一文中了解到二进制匹配机制,现在该是付诸行动、动手优化Erlang程序匹配操作的时候了。

先看来自《 Efficiency Guide User's Guide》的两个Erlang函数实例,它们的功能都是判断一个list和一个binary的内容是否相等。

test.erl
-module(test).
-compile(export_all).


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


opt_eq(<<H,T1/binary>>, [H|T2]) ->   %% 分支1
    opt_eq(T1, T2);
opt_eq(<<_,_/binary>>, [_|_]) ->     %% 分支2
    false;
opt_eq(<<>>, []) ->                  %% 分支3
    true.
对比上面两个函数,依我个人习惯,在Erlang编程时会选择第一种写法。
这两个函数功能上来说是一样的,它们的内部实现会有什么不同呢?

Erlang语言在二进制匹配方面做了一些特别的优化工作,但并不是任何情景下都能利用这一特性。
至于我们编写的Erlang代码中有没有用到这些优化特性,经常是不明确的。
为了解决这一问题,Erlang为我们提供了打印二进制优化信息的方法。

方法一
erlc +bin_opt_info xxx.erl
编译时加上+bin_opt_info选项。

方法二
export ERL_COMPILER_OPTIONS=bin_opt_info
导出一个系统的环境变量,然后按正常编译。

加上优化选项编译本例: erlc +bin_opt_info test.erl
打印了以下信息:
test.erl:4: Warning: INFO: matching anything else but a plain variable to the left of binary pattern will prevent delayed sub binary optimization; SUGGEST changing argument order
test.erl:4: Warning: NOT OPTIMIZED: called function non_opt_eq/2 does not begin with a suitable binary matching instruction
test.erl:11: Warning: OPTIMIZED: creation of sub binary delayed
初看这些信息,还不太明白它说的是什么。为了弄明白,我们来分析一下它的汇编指令。

(关于汇编指令的介绍可参见《实例分析Erlang的汇编指令》一文)


erlc +"'S'" test.erl
%% test.S

{function, non_opt_eq, 2, 2}.
  {label,1}.
    {line,[{location,"test.erl",4}]}.
    {func_info,{atom,test},{atom,non_opt_eq},2}.
  {label,2}.
    %% 如果R0为空列表,则返回true
    {test,is_nonempty_list,{f,4},[{x,0}]}.
    %% 从list中取头元素送R2
    {get_list,{x,0},{x,2},{x,3}}.
    %% 初始化match context二进制
    {test,bs_start_match2,{f,1},4,[{x,1},0],{x,4}}.
    %% 从二进制流中取出一个整型(1字节)
    {test,bs_get_integer2,
          {f,1},
          5,
          [{x,4},
           {integer,8},
           1,
           {field_flags,[{anno,[4,{file,"test.erl"}]},unsigned,big]}],
          {x,5}}.
    %% 取出剩余的二进制,并创建新的sub binary,送R6
    {test,bs_get_binary2,
          {f,1},
          6,
          [{x,4},
           {atom,all},
           8,
           {field_flags,[{anno,[4,{file,"test.erl"}]},unsigned,big]}],
          {x,6}}.
    %% '%'是注释,打印优化信息
    {'%',{no_bin_opt,{{label,2},no_suitable_bs_start_match},
                     [4,{file,"test.erl"}]}}.
    {test,is_eq_exact,{f,3},[{x,5},{x,2}]}.
    %% R6(新创建的sub binary)送R1,为下一次迭代准备参数
    {move,{x,6},{x,1}}.
    {move,{x,3},{x,0}}.
    {call_only,2,{f,2}}.
  {label,3}.
    {move,{atom,false},{x,0}}.
    return.
  {label,4}.
    {test,is_nil,{f,1},[{x,0}]}.
    {test,is_eq_exact,{f,1},[{x,1},{literal,<<>>}]}.
    {move,{atom,true},{x,0}}.
    return.




{function, opt_eq, 2, 6}.
  {label,5}.
    {line,[{location,"test.erl",11}]}.
    {func_info,{atom,test},{atom,opt_eq},2}.
  {label,6}.
    %% 初始化match context二进制,结果送R0
    {test,bs_start_match2,{f,5},2,[{x,0},0],{x,0}}.
    %% 从二进制流中取出一个整型(1字节)
    {test,bs_get_integer2,
          {f,8},
          2,
          [{x,0},
           {integer,8},
           1,
           {field_flags,[{anno,[11,{file,"test.erl"}]},unsigned,big]}],
          {x,2}}.
    {'%',{bin_opt,[11,{file,"test.erl"}]}}.
    {test,bs_test_unit,{f,9},[{x,0},8]}.
    %% R0送R3
    {move,{x,0},{x,3}}.
    {test,is_nonempty_list,{f,9},[{x,1}]}.
    {get_list,{x,1},{x,4},{x,5}}.
    {test,is_eq_exact,{f,7},[{x,4},{x,2}]}.
    {move,{x,5},{x,1}}.
    %% R3送回R0
    {move,{x,3},{x,0}}.
    {call_only,2,{f,6}}.
  {label,7}.
    {move,{atom,false},{x,0}}.
    return.
  {label,8}.
    {test,bs_test_tail2,{f,9},[{x,0},0]}.
    {test,is_nil,{f,9},[{x,1}]}.
    {move,{atom,true},{x,0}}.
    return.
  {label,9}.
    {bs_context_to_binary,{x,0}}.
    {jump,{f,5}}.
上面的指令中可以明显看出 opt_eq函数的分支2没有创建sub binary,而是直接把match context类型的二进制作为下一次迭代的参数。
bs_start_match2指令也做了优化,如果参数是match context类型就不会再次创建match context二进制。

non_opt_eq函数为什么就没有进行这些优化?
要解答这个问题,先要了解二进制的创建机制,关于这方面的内容前面的文章里已经介绍过,这里就不再赘述。

在本例中,sub binary是否要被创建,取决于编译器是否能确定match context二进制不被共享。

像opt_eq函数,binary匹配作为第一个参数,可以确定函数的每一个分支都首先会进行匹配操作,只要binary不为空,匹配前都要先创建match context(如果它不是match context),这些情况编译器都是可以确定的,所以没有必要每次迭代都创建sub binary,故可进行优化。

而 non_opt_eq函数,在某个分支里binary是否要进行匹配操作要依赖于第一个参数,也可能在某个分支里被共享,如:
non_opt_eq([H|T1], <<H,T2/binary>>) ->
    non_opt_eq(T1, T2);
non_opt_eq([_|_], T) ->
    mod:fun(T).

目前所有类似以下形式的代码都不会被优化:
match_body([0|_], <<H,_/binary>>) ->
    done;
如果编译时加了优化选项,会提示你调整参数的顺序(SUGGEST changing argument order)。

就non_opt_eq这个函数而言,理论上编译器是可以对它进行优化的,不过目前没有实现,或许将来的版会实现这一点。

小结

如果函数参数中有匹配操作的参数,根据实际的情况尽量将它放在首位。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个从文件中读取字符串并将其转换为二进制字符串的Erlang示例代码: ``` -module(file_example). -export([read_file/1]). read_file(FileName) -> {ok, IoDevice} = file:open(FileName, [read]), read_lines(IoDevice, <<>>), % 初始值为一个空二进制字符串 file:close(IoDevice). read_lines(IoDevice, Content) -> case io:get_line(IoDevice, "") of eof -> Content; Line -> % 将字符串转换为二进制字符串 BinaryLine = list_to_binary(Line), read_lines(IoDevice, <<Content/binary, BinaryLine/binary>>) end. ``` 该函数首先使用`file:open/2`函数打开文件,然后循环读取文件中的每一行,将每一行字符串转换为二进制字符串,并将其附加到一个初始值为空的二进制字符串中。最后,函数返回这个二进制字符串。 在`read_lines/2`函数中,我们使用`io:get_line/2`函数来读取文件中的每一行。读取到的每一行都是一个字符串类型,我们需要将其转换为二进制字符串类型,以便于后续处理。我们使用`list_to_binary/1`函数将字符串转换为二进制字符串。 注意,在将二进制字符串附加到`Content`变量时,我们使用了`<<Content/binary, BinaryLine/binary>>`的语法,它表示将两个二进制字符串拼接在一起。这个语法可以让我们更有效地将二进制字符串连接在一起。 你可以将上面的代码保存到一个名为`file_example.erl`的文件中,并在Erlang Shell中加载和运行它。例如,如果你在Erlang Shell中输入以下命令,它将读取文件`test.txt`中的字符串,并将其输出为二进制字符串: ``` 1> c(file_example). {ok,file_example} 2> file_example:read_file("test.txt"). <<"hello world\nthis is a test.\n">> ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值