Erlang数据类型的表示和实现(5)——binary

binary 是 Erlang 中一个具有特色的数据结构,用于处理大块的“原始的”字节块。如果没有 binary 这种数据类型,在 Erlang 中处理字节流的话可能还需要像列表或元组这样的数据结构。根据之前对这些数据结构 Eterm 的描述,数据块中的每一个字节都需要一个或两个机器字来表达,明显空间利用率低,因此 binary 是一种空间高效的表示形式。

在 binary 对字节序列处理能力的基础上,Erlang 进一步泛化 binary 的功能,提供了 bitstring 数据结构,让开发者能打破字节的边界,能在 bit 层面上操作原始数据块。bitstring 的 bit 层次的模式匹配功能特别适用于网络编程中网络协议数据包的解析和文件解析等操作。

本文从实际的需求出发,从简单到复杂,逐步讨论 Erlang 中 binary 和 bitstring 的实现及优化。本文会介绍 binary 相关数据结构的 Eterm 以及在 Erlang 虚拟机内部的表达形式,并结合具体的示例程序和编译器生成的 beam 字节码及对应的虚拟机代码讨论 Erlang 对 binary 和 bitstring 的操作所做的优化。

下面首先讨论最简单的 binary —— heap binary。

heap binary

heap binary 是直接放在进程堆中的 binary,也就是说整个 binary 的数据都在进程堆中,就好像其他 boxed 数据结构一样。下图展示了 heap binary 在堆中的表现形式。

这是一个典型的 boxed 对象,第一个字的标签表示了这个对象的类型,arity 表示后面跟了几个字。第二个字则表示这个 binary 中的字节数。接下来就是 arity - 1 个字,从低地址到高地址原样保存了 binary 数据的拷贝。

由于 heap binary 直接放在堆中,属于“小”数据,进程间要发送这种 binary 消息的时候会涉及到复制,因此目前 Erlang 虚拟机代码中将 heap binary 大小限定为 64 字节。在创建 binary 的时候,如果事先确定 binary 中数据字节数小于等于 64 字节,那么 Erlang 虚拟机就会选择在堆中直接创建 heap binary。

如果在创建 binary 的时候,确定 binary 中数据的字节数大于 64 字节,那么 Erlang 虚拟机就会创建 refc binary。

refc binary

refc binary 是保存在 Erlang 虚拟机内存中,所有 Erlang 进程堆之外的区域中的 binary。Erlang 进程之间可以共享这种 binary。当一个 Erlang 进程给另一个 Erlang 进程发送这种 binary 的时候,理想情况下只需要发送一个引用即可,因此可以避免复制的开销和内存的开销。由于这种 binary 是可以被多个进程共享的,因此为了跟踪这种 binary 的使用,Erlang 虚拟机采用了引用计数的方式,因此这种 binary 得名为 refc binary,即 reference-counted binary 的意思。

由于 refc binary 是共享的,所以需要通过两个部分描述,一个部分是 binary 数据本身,另一个部分是对 binary 数据的引用,即 Erlang 进程堆中的 ProcBin 对象。下图展示了 ProcBin 和 Binary 对象之间的关系。

从图中可以看出,ProcBin 对象是在进程堆中的 boxed 对象,并且通过 next 指针串联起来了。进程控制块中有一个 ErlOffHeap 数据,这个数据是进程所有 off-heap(即“堆外”)数据的链表的头,first 指向第一个 ProcBin。目前 Erlang 进程只有 refc binary 这一种 off-heap 数据,不过以后有可能会有更多类型的 off-heap 数据类型,因为 off-heap 这种名称看上去很 general。ErlOffHeap 数据还有一个字段 overhead,这个字段记录了所有 off-heap 数据大小的总和,这个值会用于垃圾回收,如果这个 overhead 超过了进程的 vheap(虚拟堆)限制,则会进行垃圾回收。vheap 也是一个 general 的概念,尽管目前仅用于 binary。有关 vheap 的初始化、增长和对垃圾回收的控制,请参阅这篇博文[注5]。另外提一下,在 Erlang 虚拟机的代码中,有一个宏 MSO,经常能看到类似 MSO(c_p).overhead 和 MSO(c_p).first 这样的调用,MSO(c_p) 宏实际上获得的就是当前进程 c_p 的 ErlOffHeap 数据。估计 MSO 是 memory shared object 的简写吧,共享内存对象和 off-heap 对象应该是同一个意思。

ProcBin 对象的第一个字就是标准的 boxed 对象头。接下来的 size 表示 ProcBin 指向的 Binary 对象的实际字节数,next 指向进程中的下一个 ProcBin,val 指向共享内存区域中的实际 Binary 对象,bytes 则指向实际 Binary 对象中的真正的数据块。flags 是 ProcBin 相关的标志位,和 binary 操作的优化有关,后面会详细解释。

再来看 Binary 对象。Binary 对象完整地包含了 binary 实际要表达的数据,因此是一个可变大小的对象,实际数据在 orig_bytes 数组中。orig_size 表示 orig_bytes 数组中的字节数。refc 则是引用计数,初始化一个 refc binary 的时候对应的 Binary 的 refc 初始化为 1。refc 降为 0 的时候表示可以回收。由于 Binary 可以被多个进程访问,因此 refc 在 SMP 版本的 Erlang 虚拟机上是一个原子变量。Binary 的标志位 flags 主要由 Erlang 虚拟机中其他部分使用(标记 Binary 本身的不同类型,可以将 Binary 理解为“基类”,其他类型的 Binary 可能会添加一些特殊的功能,例如 ErtsMagicBinary 中添加了特殊的“析构函数”。),和 binary 本身操作无关,因此本文不详细讨论。

上面我们讨论了两种包含真实数据的 binary:heap 和 refc binary,这两种 binary 都是容器,分别对应了 boxed 对象的 header 标签 1000 和 1001。除此之外,我们还可以在 header 标签的列表中看到另外两种和 binary 相关的 header:表示 binary 匹配状态的 0001,以及表示 sub binary 的 1010。这两种 binary 对象本身并不包含实际的 binary 数据,而是引用其他 binary 中的部分数据。下面我们先看 sub binary。此外,bitstring 的实现也和 sub binary 有关。

sub binary 和 bitstring

sub binary 就是子 binary,表示 binary 中的部分内容。比如我们调用 BIF split_binary/2 的时候,如果参数正确,会将原来的 binary 分割为两个部分,得到两个 binary。这个 BIF 调用当然可以创建两个新的 heap 或 refc binary,然后分别将对应的数据复制到两个新的 binary。由于 Erlang 中的变量都是 immutable 的,所以我们可以认为一个 binary 在创建了之后不会被修改。因此,这种创建新 binary 并复制的操作显然是低效且浪费空间的。

为了在分割 binary 的时候能复用原有的数据,Erlang 虚拟机内部引入了 sub binary 类型(Erlang 程序员在 Erlang 语言的层面感知不到)。Erlang 的 split_binary/2 调用生成的就是两个 sub binary,然后将这两个 sub binary 放在一个二元组中返回给调用者。下图展示了表示 sub binary 的 boxed 对象的结构,即图中的 ErlSubBin 部分,并展示了 split_binary/2 对一个 refc binary 操作之后的示例结果。

我们首先看 sub binary 的结构,在 Erlang 虚拟机代码中定义如下: 

1 typedef struct erl_sub_bin {
2     Eterm thing_word;        /* Subtag SUB_BINARY_SUBTAG. */
3     Uint size;            /* Binary size in bytes. */
4     Uint offs;            /* Offset into original binary. */
5     byte bitsize; 
6     byte bitoffs; 
7     byte is_writable;        /* The underlying binary is writable */
8     Eterm orig;            /* Original binary (REFC or HEAP binary). */
9 } ErlSubBin;

和其他 boxed 对象的结构体一样,thing_word 是 boxed 对象的 header。size 表示这个 sub binary 引用的数据的大小,offs 表示这个 sub binary 引用的具体 binary 中的偏移值。bitsize 和 bitoffs 和 bistring 有

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值