我们先来重温一下《
Erlang数据类型的内部实现》一文。
引用计数二进制可由以下两个结构体组合起来描述:
引用计数二进制包含两部分:
堆二进制是最大为64字节的小二进制,它被直接存储在进程堆里。对于堆二进制,当GC或被作为消息发送时,它会被复制,在GC方面不需要做特别的处理。
子二进制是在使用函数split_binary/2或进行二进制匹配时产生的。它可以引用现有的refc binary或heap binary的一部分。充分使用子二制这一特性,可以提升二进制匹配操作的性能,因为匹配结果不用从原二进制中拷贝。
match context和子二进制类似。为了匹配操作可以更快速,对比于子二进制,它做了一些优化,增加了直接指向二进制数据的指针(ErlBinMatchState->mb->base),当从二进制数据中匹配出字段值后直接移动指针即可。
sub binary和match context是对refc或heap二进制数据中某部分的描述,或者说是引用。
Erlang中封装binary数据指针时,尾部都会被贴上boxed标签(即最后2位为10B),
指针所指向的内容首4字节(确切的说是sizeof(Eterm)字节)是一个header,
从《Erlang数据类型的内部实现》一文中关于header的一段注释中可以看到:
/*
* HEADER representation:
*
* aaaaaaaaaaaaaaaaaaaaaaaaaatttt00 arity:26, tag:4
*
* HEADER tags:
*
* 0001 BINARY_AGGREGATE |
* 1000 REFC_BINARY | |
* 1001 HEAP_BINARY | BINARIES |
* 1010 SUB_BINARY | |
*
*/
#define BIN_MATCHSTATE_SUBTAG (0x1 << _TAG_PRIMARY_SIZE)
#define _BINARY_XXX_MASK (0x3 << _TAG_PRIMARY_SIZE)
#define REFC_BINARY_SUBTAG (0x8 << _TAG_PRIMARY_SIZE) /* BINARY */
#define HEAP_BINARY_SUBTAG (0x9 << _TAG_PRIMARY_SIZE) /* BINARY */
#define SUB_BINARY_SUBTAG (0xA << _TAG_PRIMARY_SIZE) /* BINARY */
从上面header可得知二进制数据在底层可以分为四类。
表1:Erlang Binary的四种类型
Type Name | Header Tag (B) | Tag Name | #define SUBTAG |
---|---|---|---|
refc binaries | 1000 | REFC_BINARY | REFC_BINARY_SUBTAG |
heap binaries | 1001 | HEAP_BINARY | HEAP_BINARY_SUBTAG |
sub binary | 1010 | SUB_BINARY | SUB_BINARY_SUBTAG |
match context | 0001 | BINARY_AGGREGATE | BIN_MATCHSTATE_SUBTAG |
refc binaries (reference-counted binaries) 引用计数二进制
引用计数二进制可由以下两个结构体组合起来描述:
typedef struct proc_bin {
Eterm thing_word; /* Subtag REFC_BINARY_SUBTAG. */
Uint size; /* Binary size in bytes. */
struct erl_off_heap_header *next;
Binary *val; /* 指向Binary结构 */
byte *bytes; /* Pointer to the actual data bytes. */
Uint flags; /* Flag word. */
} ProcBin;
typedef struct binary {
UWord flags;
erts_refc_t refc; /* 引用计数 */
ERTS_BINARY_STRUCT_ALIGNMENT
SWord orig_size;
char orig_bytes[1]; /* to be continued */
// 作为二进制数据的容器1,这里会被动态扩展后继的内存空间
} Binary;
引用计数二进制包含两部分:
- ProcBin,它被保存在进程堆里,ProcBin->val指向Binary。所有的ProcBin都是同一个链表的一部分,GC可以跟踪它们,当ProcBin移除时Binary->refc引用计数会减一。
- Binary,它徘徊在所有的进程堆之外,可被N个ProcBin引用,Binary->refc保存了引用计数,当引用计数为0时它将被抛弃。
heap binary 堆二进制
typedef struct erl_heap_bin {
Eterm thing_word; /* Subtag HEAP_BINARY_SUBTAG. */
Uint size; /* Binary size in bytes. */
Eterm data[1]; /* The data in the binary. */
// 作为二进制数据的容器2,这里会被动态扩展后继的内存空间
} ErlHeapBin;
堆二进制是最大为64字节的小二进制,它被直接存储在进程堆里。对于堆二进制,当GC或被作为消息发送时,它会被复制,在GC方面不需要做特别的处理。
sub binary 子二进制
顾名思义,它就是用来描述现有二制进的某一部分的。
typedef struct erl_sub_bin {
Eterm thing_word; /* Subtag SUB_BINARY_SUBTAG. */
Uint size; /* Binary size in bytes. */
Uint offs; /* Offset into original binary. */
byte bitsize;
byte bitoffs;
byte is_writable; /* The underlying binary is writable */
Eterm orig; /* Original binary (REFC or HEAP binary). */
} ErlSubBin;
子二进制是在使用函数split_binary/2或进行二进制匹配时产生的。它可以引用现有的refc binary或heap binary的一部分。充分使用子二制这一特性,可以提升二进制匹配操作的性能,因为匹配结果不用从原二进制中拷贝。
match context
对于这个官方名字,有点纠结,想不到合适的中文名来描述它。在源码中没有看到这个名字的出现,在表1中可以看到与此名字对应的其它描述。
// This structure represents a binary to be matched.
typedef struct erl_bin_match_buffer {
Eterm orig; /* 指向原始二进制数据包(ProcBin或ErlHeapBin) */
byte* base; /* 直接指向二进制数据(ProcBin->bytes或ErlHeapBin->data) */
Uint offset; /* Offset in bits. */
size_t size; /* Size of binary in bits. */
} ErlBinMatchBuffer;
typedef struct erl_bin_match_struct{
// header(thing_word)的求值过程为:
// (size << 6) | (TAG_PRIMARY_HEADER | BIN_MATCHSTATE_SUBTAG)
Eterm thing_word;
ErlBinMatchBuffer mb; /* Present match buffer */
Eterm save_offset[1]; /* Saved offsets */
} ErlBinMatchState;
match context和子二进制类似。为了匹配操作可以更快速,对比于子二进制,它做了一些优化,增加了直接指向二进制数据的指针(ErlBinMatchState->mb->base),当从二进制数据中匹配出字段值后直接移动指针即可。
小结
refc binaries和heap binaries是装二进制数据的容器。sub binary和match context是对refc或heap二进制数据中某部分的描述,或者说是引用。