可以通过以下方式有效地构建二进制:

my_list_to_binary(List) ->

my_list_to_binary(List, <<>>).

my_list_to_binary([H|T], Acc) ->

my_list_to_binary(T, <<Acc/binary,H>>);

my_list_to_binary([], Acc) ->

Acc.

二进制可以像这样有效地匹配:

my_binary_to_list(<<H,T/binary>>) ->

[H|my_binary_to_list(T)];

my_binary_to_list(<<>>) -> [].

4.1如何实现二进制

在内部,二进制和位串以相同的方式实现。在本节中,它们被称为二进制,因为这就是它们在模拟器源代码中的名称。

内部有四种类型的二进制对象:

  • 两个是二进制数据的容器,它们被称为:

    • Refc Binaries引用计数二进制的缩写)
    • 堆二进制
  • 两个仅仅是对二进制一部分的引用,它们被称为:
    • 子二进制
    • 匹配上下文

Refc二进制

Refc二进制包含两个部分:

  • 存储在进程堆上的对象,称为ProcBin
  • 二进制对象本身,存储在所有进程堆的外部

二进制对象可由任意数量的进程中的任意数量的ProcBins引用。该对象包含一个引用计数器,以跟踪引用的数量,以便在最后一个引用消失时可以将其删除。

进程中的所有ProcBin对象都是链接列表的一部分,因此,当ProcBin消失时,垃圾收集器可以跟踪它们并减少二进制中的引用计数器。

堆二进制

堆二进制是小型二进制,最多64个字节,并直接存储在进程堆中。当进程被垃圾回收并且作为消息发送时,它们被复制。它们不需要垃圾收集器进行任何特殊处理。

子二进制

引用对象子二进制匹配上下文可以引用refc二进制或堆二进制的一部分。

子二进制通过创建split_binary/2,并且当二进制以二进制模式匹配的。子二进制是对另一个二进制(refc或堆二进制,而不是对另一个子二进制)的一部分的引用。因此,匹配二进制相对便宜,因为从不复制实际的二进制数据。

匹配上下文

匹配上下文类似于子二进制,但对于二进制匹配被优化。例如,它包含一个指向二进制数据的直接指针。对于从二进制中匹配的每个字段,匹配上下文中的位置会增加。

编译器试图避免生成用于创建子二进制的代码,而只是在不久之后创建一个新的匹配上下文并丢弃该子二进制。保留匹配上下文,而不是创建子二进制。

如果编译器知道不会共享匹配上下文,则只能进行此优化。如果将其共享,则Erlang的功能属性(也称为参照透明性)将中断。

4.2构造二进制

运行时系统特别优化了附加到二进制或位串的操作:

<<Binary/binary, ...>>

<<Binary/bitstring, ...>>

当运行时系统处理优化(而不是编译器)时,在极少数情况下优化不起作用。

为了解释它是如何工作的,让我们逐行检查以下代码:

Bin0 = <<0>>,                    %% 1

Bin1 = <<Bin0/binary,1,2,3>>,    %% 2

Bin2 = <<Bin1/binary,4,5,6>>,    %% 3

Bin3 = <<Bin2/binary,7,8,9>>,    %% 4

Bin4 = <<Bin1/binary,17>>,       %% 5 !!!

{Bin4,Bin3}                      %% 6

  • 第1行(标有%% 1注释)将堆二进制分配给Bin0变量。
  • 第2行是追加操作。由于Bin0尚未参与追加操作,新的REFC二进制被创建和内容Bin0被复制到它。refc二进制的ProcBin部分的大小设置为存储在二进制中的数据的大小,而二进制对象分配了额外的空间。二进制对象的大小是Bin1或256的大小的两倍,以较大者为准。在这种情况下为256。
  • 第3行更有趣。Bin1已经在附加操作中使用,并且它具有252个字节在末端未使用的存储空间,所以3个新字节被存储在那里。
  • 第4行。此处同样适用。剩下249个字节,因此存储另外3个字节没有问题。
  • 第5行。在这里,发生了一些有趣的事情。请注意,结果不追加到以前的结果Bin3,但Bin1。预期将为Bin4赋值<<0,1,2,3,17>>。还可以预期Bin3将保留其值(<<0,1,2,3,4,5,6,7,8,9>>)。显然,运行时系统无法将字节17写入二进制,因为这会将Bin3的值更改为<<0,1,2,3,4,17,6,7,8,9>>。

运行时系统会发现Bin1是上一个追加操作(不是最新的追加操作)的结果,因此它将Bin1的内容复制到新的二进制中,保留了额外的存储空间,依此类推。(这里没有解释运行时系统如何知道不允许将其写入Bin1;好奇的读者可以将其作为练习,通过读取仿真器源代码(主要是erl_bits.c)来了解如何完成此操作。)

强制复制的情况

二进制追加操作的优化要求,有一个单一ProcBin和一个单一的引用到ProcBin用于二进制。原因是可以在追加操作期间移动(重新分配)二进制对象,并且在这种情况下,必须更新ProcBin中的指针。如果将有多个ProcBin指向二进制对象,则将不可能找到并更新所有它们。

因此,对二进制的某些操作会对其进行标记,以便将来任何附加操作都将被强制复制二进制。在大多数情况下,二进制对象将同时缩小以回收分配给增长的额外空间。

当按如下所示追加到二进制时,仅从最新的追加操作返回的二进制将支持进一步的廉价追加操作:

Bin = <<Bin0,...>>

在本节开头的代码片段中,追加到Bin将很便宜,而追加到Bin0将强制创建新的二进制并复制Bin0的内容。

如果将二进制作为消息发送到进程或端口,则该二进制将缩小,并且任何进一步的追加操作会将二进制数据复制到新的二进制中。例如,在下面的代码片段中,Bin1将被复制到第三行:

Bin1 = <<Bin0,...>>,

PortOrPid!Bin1

Bin = <<Bin1,...>>   %% Bin1将被复制

如果将二进制插入到Ets表中,或者使用erlang:port_command/2将其发送到端口,或者将其传递给NIF中的enif_inspect_binary也会发生同样的情况。

匹配二进制也将导致其缩小,并且下一个追加操作将复制二进制数据:

Bin1 = <<Bin0,...>>,

<< X,Y,Z,T/binary>> = Bin1,

Bin = <<Bin1,...>>   %% Bin1将被复制

原因是匹配上下文包含指向二进制数据的直接指针。

如果进程仅保留二进制(在“循环数据”中或在进程字典中),则垃圾收集器最终可以收缩二进制。如果只保留一个这样的二进制,它将不会缩小。如果该过程稍后追加到已缩小的二进制中,则将重新分配二进制对象以放置要附加的数据。

4.3匹配二进制

让我们回顾上一节开头的示例:

my_binary_to_list (<< H,T/binary >>) ->

[H | my_binary_to_list(T)];

my_binary_to_list (<< >>) -> []。

首次调用my_binary_to_list/1时,将创建一个匹配上下文。匹配上下文指向二进制的第一个字节。1个字节被匹配,并且匹配上下文被更新以指向二进制中的第二个字节。

在这一点上,创建一个子二进制是有意义的,但是在此特定示例中,编译器发现很快将调用一个函数(在本例中为my_binary_to_list/1本身),该函数将立即创建一个新的匹配上下文并丢弃子二进制。

因此,my_binary_to_list/1会使用match上下文而不是子二进制进行调用。初始化匹配操作的指令在看到已传递给匹配上下文而不是二进制时,基本上什么也不做。

当到达二进制的末尾并且第二个子句匹配时,匹配上下文将被简单地丢弃(在下一个垃圾回收中将其删除,因为不再有对其的引用)。

总而言之,my_binary_to_list/1仅需要创建一个匹配上下文,而无需子二进制。

请注意,遍历整个二进制后,将放弃my_binary_to_list/1中的match上下文。如果迭代在到达二进制末尾之前停止,会发生什么情况?优化是否仍然有效?

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

T;

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

after_zero(T);

after_zero(<<>>) ->

<<>>.

是的,它会的。编译器将在第二个子句中删除子二进制的构建:

...

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

after_zero(T);

...

但是它将生成在第一个子句中构建子二进制的代码:

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

T;

...

因此,after_zero/1构建一个匹配上下文和一个子二进制(假定传递了一个包含零字节的二进制)。

如下代码也将得到优化:

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

编译器在第二和第三子句中删除了子二进制的构建,并向第一子句添加了一条指令,该指令将Buffer从匹配上下文转换为子二进制(如果Buffer已经是二进制,则不执行任何操作)。

但是在更复杂的代码中,如何知道是否应用了优化呢?

选项bin_opt_info

使用bin_opt_info选项可使编译器打印许多有关二进制优化的信息。可以将其提供给编译器或erlc:

erlc +bin_opt_info Mod.erl

或通过环境变量传递:

export ERL_COMPILER_OPTIONS=bin_opt_info

注意,bin_opt_info并不是要添加到Makefile的永久选项,因为它生成的所有消息都无法消除。因此,在大多数情况下,将选项传递给环境是最实用的方法。

警告如下:

./efficiency_guide.erl:60:警告:未优化:该函数返回了二进制

./efficiency_guide.erl:62:警告:已优化:匹配上下文已重用

为了更清楚地说明警告所指的代码,例如,以下示例中的警告以注释的形式插入它们所引用的子句之后,例如:

after_zero (<< 0,T/binary>>) -> %% BINARY CREATED:从函数返回二进制

T;

after_zero (<< _,T/binary >>) -> %%优化:重用匹配上下文

after_zero(T);

after_zero (<< >>) ->

<< >>。

第一个子句的警告说,不能延迟子二进制的创建,因为它将被返回。第二个子句的警告说将不会创建子二进制。

未使用的变量

编译器会确定变量是否未使用。为以下每个功能生成相同的代码:

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位将被跳过,不匹配。

4.4历史记录

R12B中的二进制处理得到了显着改善。由于在R11B中有效的代码在R12B中可能无效,反之亦然,因此本《效率指南》的较早版本包含一些有关R11B中二进制处理的信息。

【译】构造和匹配二进制(Efficiency Guide)的更多相关文章

  1. 转载:【原译】Erlang构建和匹配二进制数据(Efficiency Guide)

    转自:http://www.cnblogs.com/futuredo/archive/2012/10/19/2727204.html Constructing and matching binarie ...

  2. 转载:Erlang 函数(Efficiency Guide)

    转自:http://www.cnblogs.com/futuredo/archive/2012/10/26/2737644.html Functions 1  Pattern matching 模式匹 ...

  3. 转载:【原译】Erlang常见注意事项(Efficiency Guide)

    转自:http://www.cnblogs.com/futuredo/archive/2012/10/17/2726416.html Common Caveats(常见注意事项) Erlang/OTP ...

  4. PyQt学习随笔:Qt中Model/View中的怎么构造View匹配的Model

    老猿Python博文目录 老猿Python博客地址 在<PyQt学习随笔:Qt中Model/View相关的主要类及继承关系>介绍了Model/View架构的主要类,在实际使用时,view相 ...

  5. 转载:【原译】Erlang性能的八个误区(Efficiency Guide)

    转自:http://www.cnblogs.com/futuredo/archive/2012/10/16/2725770.html The Eight Myths of Erlang Perform ...

  6. 转载:【原译】Erlang列表处理(Efficiency Guide)

    转自:http://www.cnblogs.com/futuredo/archive/2012/10/22/2734186.html List handling 1  Creating a list ...

  7. Erlang 位串和二进制数据

    http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=25876834&id=3300393 因为在本人工作中,服务端Erla ...

  8. hdu 4685 二分匹配+强连通分量

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=4685 题解: 这一题是poj 1904的加强版,poj 1904王子和公主的人数是一样多的,并且给出 ...

  9. s14 第5天 时间模块 随机模块 String模块 shutil模块(文件操作) 文件压缩(zipfile和tarfile)shelve模块 XML模块 ConfigParser配置文件操作模块 hashlib散列模块 Subprocess模块(调用shell) logging模块 正则表达式模块 r字符串和转译

    时间模块 time datatime time.clock(2.7) time.process_time(3.3) 测量处理器运算时间,不包括sleep时间 time.altzone 返回与UTC时间 ...

随机推荐

  1. React的第二种使用方法----脚手架方式

    一.React的第二种使用方法-----脚手架 1.前提:Node.js >8.10 2.下载全局脚手架工具 npm  i  -g  create-react-app 3.运行全局脚手架工具,创 ...

  2. 如何在本地调试你的 Spark Job

    生产环境的 Spark Job 都是跑在集群上的,毕竟 Spark 为大数据而生,海量的数据处理必须依靠集群.但是在开发Spark的的时候,不可避免我们要在本地进行一些开发和测试工作,所以如何在本地用 ...

  3. Solr-常见问题汇总(持续更新)

    本文主要记录solr使用中遇到的一些常见问题及命令 关于solrConfig.xml的配置博客:https://blog.csdn.net/yuh_LLllccy/article/details/88 ...

  4. ShoneSharp语言(S#)的设计和使用介绍系列(8)— 最炫“公式”风

    ShoneSharp语言(S#)的设计和使用介绍 系列(8)— 最炫“公式”风 作者:Shone 声明:原创文章欢迎转载,但请注明出处,https://www.cnblogs.com/ShoneSha ...

  5. Jmeter基础-下载与安装

    jmeter下载与安装 下载网址:http://jmeter.apache.org/download_jmeter.cgi windows点击下载zip文件 该版本需要JDK1.8及以上版本 免安装, ...

  6. JAVA 基础知识。程序运方法。

    dos     常用命令    dir     查看此文件夹目录下的所有程序    cd..    返回上一层目录    盘符:  直接切换至相应的盘符    cd 目录 切换至指定的目录    cd ...

  7. 利用pandas进行数据子集的获取

  8. 面向对象案例 - 学生信息管理系统V1.0

    学生管理系统项目[所有知识点整合] 1. 学生管理系统项目 尝试完成以下功能 实体类: 学生类: id, 姓名,年龄,性别,成绩 需要使用数组保存学生信息 Student[] allStu 需要完成的 ...

  9. .NET Core 反射获取所有控制器及方法上特定标签

    .NET Core 反射获取所有控制器及方法上特定标签 有个需求,就是在. NET Core中,我们想在项目 启动时,获取LinCmsAuthorizeAttribute这个特性标签所有出现的地方,把 ...

  10. PIC单片机的i2c的程序

    #include<pic.h>#define uchar unsigned char#define uint unsigned int#define add 0xaa__CONFIG(0x ...