Erlang二进制创建的内部机制和优化(二)
2013-07-27 08:20
309 查看
Erlang二进制创建的内部机制和优化(一)
这一节以实例分析的方式继续探索二进制创建的内部机制。
为了验证上一节的内容,首先在erl_bits.c中的erts_bs_appen函数里加入一些调试输出。
发现没有调试输出,猜测是没有调用append函数。
继续看看test:t/4的结果。
上面的三个测试结果,让我们产生了很多不解,下面就将这些迷团一一解开。
先将test.erl通过下面的命令编译成erlang 指令。
erlc +\'S\' test.erl
test.S
而test:t/4是通过参数动态创建的,从中可以看到它调用的函数及执行流程。
最不解的是在Erlang Shell中执行的结果,为什么和预期的相差这么远?
Erlang的二进制append操作过程中,由于ProcBin(见#MARK_C处)所引用的binary在扩展空间时可能会被移动,此时就必段更新ProcBin的引用指针(ProcBin->val),
如果这时有别的ProcBin引用了这个binary,就可能会出问题,这也违反了变量不可变的原则,
所以,当append操作的过程中,如果出中间执行了一些会影响ProcBin的指令,sub binary就会被设置为以后不可再写。
出现以下情况之一,都会被设为不可再写:
当作结果返回
当作消息被发送(PortOrPid ! Bin1)
当作普通变量插入ETS表
当作普通变量进行二进制匹配操作
在Erlang Shell中,测试代码可以写成这样:
例如,在Erlang Shell中,Bin3 = <<Bin1/binary,4>> 这一句的执行过程如下:
先创建一个大小为可以容器Bin1的heap binary:
小结
在Erlang编程中,我们要了解Binary二进制的创建场情是否会发挥append的优化特性,特别是在网络编程中的收包解包,要充分利用这一特性以提高效率。
编译器会对erl代码进行优化,测试时要注意这种优化是否会影响测试结果。
Erlang Shell中执行代码时,要了解它的执程流程及与文件代码的区别,以免出现莫名的情况。
erl代码可以通过erlc +\'E\' file.erl和erlc +\'S\' file.erl生成代码的扩展文件和指令文件,观察程序执行过程。
这一节以实例分析的方式继续探索二进制创建的内部机制。
为了验证上一节的内容,首先在erl_bits.c中的erts_bs_appen函数里加入一些调试输出。
void print_bin(char *title, unsigned char *data, int len) { Uint index = 0; erts_fprintf(stderr,"%s {", title); while(index < len){ if(index) erts_fprintf(stderr,",%u", data[index]); else erts_fprintf(stderr,"%u", data[index]); index++; } erts_fprintf(stderr, "} (len:%d)\n", len); } Eterm erts_bs_append(Process* c_p, Eterm* reg, Uint live, Eterm build_size_term, Uint extra_words, Uint unit) { Eterm bin; /* Given binary */ Eterm* ptr; Eterm hdr; ErlSubBin* sb; ProcBin* pb; Binary* binp; Uint heap_need; Uint build_size_in_bits; Uint used_size_in_bits; Uint unsigned_bits; ERL_BITS_DEFINE_STATEP(c_p); // 需要创建的二进制的位数: build_size_in_bits if (is_small(build_size_term)) { Sint signed_bits = signed_val(build_size_term); if (signed_bits < 0) { goto badarg; } build_size_in_bits = (Uint) signed_bits; } else if (term_to_Uint(build_size_term, &unsigned_bits)) { build_size_in_bits = unsigned_bits; } else { c_p->freason = unsigned_bits; return THE_NON_VALUE; } // 测试输出 erts_fprintf(stderr,"*** append start *** " "build_size_in_bits:%d, heap_top:%p\n", build_size_in_bits, c_p->htop); bin = reg[live]; if (!is_boxed(bin)) { badarg: c_p->freason = BADARG; return THE_NON_VALUE; } ptr = boxed_val(bin); // 取出二进制数据流中的header hdr = *ptr; if (!is_binary_header(hdr)) { goto badarg; } // #MARK_A if (hdr != HEADER_SUB_BIN) { // 非子二进制,不可写 erts_fprintf(stderr, "not_sub_bin, not_writable, header:%X\n", hdr); // if((hdr & _HEADER_SUBTAG_MASK) == HEAP_BINARY_SUBTAG){ // erts_fprintf(stderr, "be_heap_bin, not_writable\n", hdr); // }else{ // erts_fprintf(stderr, "not_sub_bin, not_writable, header:%X\n", hdr); // } goto not_writable; } sb = (ErlSubBin *) ptr; if (!sb->is_writable) { // is_writable==0,不可写 erts_fprintf(stderr, "not_writable is_writable==0\n"); goto not_writable; } pb = (ProcBin *) boxed_val(sb->orig); print_bin("pb->bytes:", (char *)pb->bytes, pb->size); // 必须是refc binary ASSERT(pb->thing_word == HEADER_PROC_BIN); if ((pb->flags & PB_IS_WRITABLE) == 0) { // 标明了不可写 erts_fprintf(stderr, "not_writable (pb->flags & PB_IS_WRITABLE) == 0\n"); goto not_writable; } /* * OK, the binary is writable. */ // 测试输出 erts_fprintf(stderr, "writable\n"); erts_bin_offset = 8*sb->size + sb->bitsize; if (unit > 1) { if ((unit == 8 && (erts_bin_offset & 7) != 0) || (erts_bin_offset % unit) != 0) { goto badarg; } } used_size_in_bits = erts_bin_offset + build_size_in_bits; // 原来的sub binary设为不可写,因为后继空间将要被写入数据 // #MARK_B sb->is_writable = 0; /* Make sure that no one else can write. */ erts_fprintf(stderr, "pb->size: from %ld extend to %ld\n", pb->size, NBYTES(used_size_in_bits)); // 扩展到所需大小 pb->size = NBYTES(used_size_in_bits); pb->flags |= PB_ACTIVE_WRITER; /* * Reallocate the binary if it is too small. */ binp = pb->val; // 如果容器的空间不足,则重新分配容器大小到所需的二倍 if (binp->orig_size < pb->size) { Uint new_size = 2*pb->size; binp = erts_bin_realloc(binp, new_size); binp->orig_size = new_size; // 注意:重新分配空间以后,pb->val指针会被改变, // 所以此用的binary不能被外部引用 // #MARK_C pb->val = binp; pb->bytes = (byte *) binp->orig_bytes; } erts_current_bin = pb->bytes; // 测试输出 erts_fprintf(stderr, "Binary Size:%ld, Binary Refc:%ld\n", binp->orig_size, binp->refc); print_bin("new pb->bytes:", (char *)pb->bytes, pb->size); /* * Allocate heap space and build a new sub binary. */ reg[live] = sb->orig; heap_need = ERL_SUB_BIN_SIZE + extra_words; if (c_p->stop - c_p->htop < heap_need) { (void) erts_garbage_collect(c_p, heap_need, reg, live+1); } // 创建一个新的sub binary,指向原二进制的开头, // 相比原来的sub binary,这里只是把空间大小扩展到所需值 sb = (ErlSubBin *) c_p->htop; // 从堆顶写入 // 进程堆顶上升ERL_SUB_BIN_SIZE(20)字节 c_p->htop += ERL_SUB_BIN_SIZE; sb->thing_word = HEADER_SUB_BIN; sb->size = BYTE_OFFSET(used_size_in_bits); sb->bitsize = BIT_OFFSET(used_size_in_bits); sb->offs = 0; sb->bitoffs = 0; // 最新的sub binary,设为可写 // 也就是说,在一系列的append操作中,只有最后一个sub binary是可写的 sb->is_writable = 1; sb->orig = reg[live]; erts_fprintf(stderr, "--- new_sub_binary_ok --- new_heap_top:%p\n\n", c_p->htop); return make_binary(sb); /* * The binary is not writable. We must create a new writable binary and * copy the old contents of the binary. */ not_writable: { Uint used_size_in_bytes; /* Size of old binary + data to be built */ Uint bin_size; Binary* bptr; byte* src_bytes; Uint bitoffs; Uint bitsize; Eterm* hp; /* * Allocate heap space. */ heap_need = PROC_BIN_SIZE + ERL_SUB_BIN_SIZE + extra_words; if (c_p->stop - c_p->htop < heap_need) { (void) erts_garbage_collect(c_p, heap_need, reg, live+1); bin = reg[live]; } hp = c_p->htop; /* * Calculate sizes. The size of the new binary, is the sum of the * build size and the size of the old binary. Allow some room * for growing. */ ERTS_GET_BINARY_BYTES(bin, src_bytes, bitoffs, bitsize); erts_bin_offset = 8*binary_size(bin) + bitsize; if (unit > 1) { if ((unit == 8 && (erts_bin_offset & 7) != 0) || (erts_bin_offset % unit) != 0) { goto badarg; } } used_size_in_bits = erts_bin_offset + build_size_in_bits; used_size_in_bytes = NBYTES(used_size_in_bits); bin_size = 2*used_size_in_bytes; // 至少256字节 bin_size = (bin_size < 256) ? 256 : bin_size; /* * Allocate the binary data struct itself. */ // 创建大小为所需空间的二倍的binary(最小值为256字节), // 它作为一个容器,存储在进程堆以外, // 进程堆里只存放引用这个binary的refc binary bptr = erts_bin_nrml_alloc(bin_size); bptr->flags = 0; bptr->orig_size = bin_size; erts_refc_init(&bptr->refc, 1); erts_current_bin = (byte *) bptr->orig_bytes; erts_fprintf(stderr, "bptr:%p, bin_size:%lu\n", bptr, bin_size); /* * Now allocate the ProcBin on the heap. */ // 创建refc binary,引用上面的binary, 并存储到进程堆 pb = (ProcBin *) hp; hp += PROC_BIN_SIZE; pb->thing_word = HEADER_PROC_BIN; // 当前设置为实际所需的大小,以后的append操作可扩展 pb->size = used_size_in_bytes; pb->next = MSO(c_p).first; MSO(c_p).first = (struct erl_off_heap_header*)pb; pb->val = bptr; pb->bytes = (byte*) bptr->orig_bytes; pb->flags = PB_IS_WRITABLE | PB_ACTIVE_WRITER; OH_OVERHEAD(&(MSO(c_p)), pb->size / sizeof(Eterm)); /* * Now allocate the sub binary and set its size to include the * data about to be built. */ // 创建sub binary,引用上面的refc binary,并设置为所需大小 sb = (ErlSubBin *) hp; hp += ERL_SUB_BIN_SIZE; sb->thing_word = HEADER_SUB_BIN; sb->size = BYTE_OFFSET(used_size_in_bits); sb->bitsize = BIT_OFFSET(used_size_in_bits); sb->offs = 0; sb->bitoffs = 0; sb->is_writable = 1; sb->orig = make_binary(pb); c_p->htop = hp; /* * Now copy the data into the binary. */ copy_binary_to_buffer(erts_current_bin, 0, src_bytes, bitoffs, erts_bin_offset); // 为了方便测试,仅输出前20字节 print_bin("dst_bytes:", erts_current_bin, 20); erts_fprintf(stderr,"-------- new_heap_top:%p --------\n\n", c_p->htop); return make_binary(sb); } }
实例1:在Erlang Shell中演示erlang的binary append操作过程
修改完Erlang C源码后,编译并启动Erlang Shell,运行测试代码:Bin0 = <<1>>, Bin1 = <<Bin0/binary,2>>, Bin2 = <<Bin1/binary,3>>, Bin3 = <<Bin1/binary,4>>, {Bin2,Bin3}.结果如下(#开头的表示注释):
Eshell V5.10.2 (abort with ^G) 1> Bin0 = <<1>>, 1> Bin1 = <<Bin0/binary,2>>, 1> Bin2 = <<Bin1/binary,3>>, 1> Bin3 = <<Bin1/binary,4>>, 1> {Bin2,Bin3}. *** append start *** build_size_in_bits:8, heap_top:0x01b1aa24 not_sub_bin, not_writable, header:64 bptr:0x01c41d18, bin_size:256 dst_bytes: {0,0,196,1,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255} (len:20) -------- new_heap_top:0x01b1aa50 -------- *** append start *** build_size_in_bits:8, heap_top:0x01b09090 not_sub_bin, not_writable, header:64 bptr:0x01c41d18, bin_size:256 dst_bytes: {1,0,196,1,255,255,15,15,15,15,15,15,15,15,15,15,15,15,15,15} (len:20) -------- new_heap_top:0x01b090bc -------- *** append start *** build_size_in_bits:8, heap_top:0x01b09164 pb->bytes: {1} (len:1) writable pb->size: from 1 extend to 2 Binary Size:256, Binary Refc:1 new pb->bytes: {1,0} (len:2) --- new_sub_binary_ok --- new_heap_top:0x01b09178 #上面两段是 Bin1 = <<Bin0/binary, 2>> 执行过程中调用append函数的输出, #首先看到not_sub_bin,header为64表示它是一个heap binary,不可写。 #接着创建了一个容器binary,大小为256字节,并且复制了Bin0, #dst_bytes第二字节以后都是可被写的空间,这里看到后面有内容是因为分配空间后没有清0 #申请了binary容器后,继续调用append进行扩展ProcBin的长度,从1扩展到2,用于容纳<<Bin0/binary,2>> *** append start *** build_size_in_bits:16, heap_top:0x01b1326c not_sub_bin, not_writable, header:64 bptr:0x01c41e38, bin_size:256 dst_bytes: {255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,170,170,170,170} (len:20) -------- new_heap_top:0x01b13298 -------- *** append start *** build_size_in_bits:8, heap_top:0x01b13340 pb->bytes: {1,2} (len:2) writable pb->size: from 2 extend to 3 Binary Size:256, Binary Refc:1 new pb->bytes: {1,2,255} (len:3) --- new_sub_binary_ok --- new_heap_top:0x01b13354 #上面两段是 Bin2 = <<Bin1/binary, 3>> 执行过程中调用append函数的输出, #仍然看到Bin1被复制后再进行扩展到3字节, #按上一节内容来说,这是不应该的,这里到底发生了什么? *** append start *** build_size_in_bits:16, heap_top:0x025bbb58 not_sub_bin, not_writable, header:64 bptr:0x01c41d18, bin_size:256 dst_bytes: {1,2,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15} (len:20) -------- new_heap_top:0x025bbb84 -------- *** append start *** build_size_in_bits:8, heap_top:0x025bbc2c pb->bytes: {1,2} (len:2) writable pb->size: from 2 extend to 3 Binary Size:256, Binary Refc:1 new pb->bytes: {1,2,15} (len:3) --- new_sub_binary_ok --- new_heap_top:0x025bbc40 #上面两段是 Bin3 = <<Bin1/binary, 4>> 执行过程中调用append函数的输出, #这里的Bin1被复制是正常的,但是,引起复制的原因是not_sub_bin,仍然是heap binary, #按上一节的内容,这里应该是: not_writable is_writable==0 #为什么Bin1是heap binary? {<<1,2,3>>,<<1,2,4>>}
实例2:将代码写入文件中编译后测试erlang的binary append操作过程
test.erl-module(test).先看看test:t/0
-export([t/4]).
-export([t/0]).
t() ->
Bin0 = <<1>>, Bin1 = <<Bin0/binary,2>>, Bin2 = <<Bin1/binary,3>>, Bin3 = <<Bin1/binary,4>>, {Bin2,Bin3}.
t(A1, A2, A3, A4) ->
Bin0 = <<A1>>,
Bin1 = <<Bin0/binary,A2>>, %% append操作1:Bin0是heap binary,不可写
Bin2 = <<Bin1/binary,A3>>, %% append操作2:Bin1可写,并设置为以后不可写
Bin3 = <<Bin1/binary,A4>>, %% append操作3:Bin1不可再写
{Bin2,Bin3}.
Eshell V5.10.2 (abort with ^G) 1> test:t(). {<<1,2,3>>,<<1,2,4>>}
发现没有调试输出,猜测是没有调用append函数。
继续看看test:t/4的结果。
Eshell V5.10.2 (abort with ^G) 1> test:t(1,2,3,4). *** append start *** build_size_in_bits:8, heap_top:0x01b09598 not_sub_bin, not_writable, header:A4 bptr:0x01c41220, bin_size:256 dst_bytes: {1,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170} (len:20) -------- new_heap_top:0x01b095c4 -------- *** append start *** build_size_in_bits:8, heap_top:0x01b095c4 pb->bytes: {1,2} (len:2) writable pb->size: from 2 extend to 3 Binary Size:256, Binary Refc:1 new pb->bytes: {1,2,170} (len:3) --- new_sub_binary_ok --- new_heap_top:0x01b095d8 *** append start *** build_size_in_bits:8, heap_top:0x01b095d8 not_writable is_writable==0 bptr:0x01c40040, bin_size:256 dst_bytes: {1,2,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170} (len:20) -------- new_heap_top:0x01b09604 -------- {<<1,2,3>>,<<1,2,4>>}上面三段输出刚好对应代码中的三个append操作,而且结果也和上一节所说的内容一致了。
上面的三个测试结果,让我们产生了很多不解,下面就将这些迷团一一解开。
先将test.erl通过下面的命令编译成erlang 指令。
erlc +\'S\' test.erl
test.S
{function, t, 0, 2}. {label,1}. {line,[{location,"test.erl",4}]}. {func_info,{atom,test},{atom,t},0}. {label,2}. {move,{literal,{<<1,2,3>>,<<1,2,4>>}},{x,0}}. return. {function, t, 4, 4}. {label,3}. {line,[{location,"test.erl",11}]}. {func_info,{atom,test},{atom,t},4}. {label,4}. {line,[{location,"test.erl",12}]}. {bs_init2,{f,0},1,0,4,{field_flags,[]},{x,4}}. {bs_put_integer,{f,0},{integer,8},1,{field_flags,[unsigned,big]},{x,0}}. {bs_append,{f,0},{integer,8},0,4,8,{x,4},{field_flags,[]},{x,0}}. {bs_put_integer,{f,0},{integer,8},1,{field_flags,[unsigned,big]},{x,1}}. {bs_append,{f,0},{integer,8},0,4,8,{x,0},{field_flags,[]},{x,1}}. {bs_put_integer,{f,0},{integer,8},1,{field_flags,[unsigned,big]},{x,2}}. {bs_append,{f,0},{integer,8},3,4,8,{x,0},{field_flags,[]},{x,2}}. {bs_put_integer,{f,0},{integer,8},1,{field_flags,[unsigned,big]},{x,3}}. {put_tuple,2,{x,0}}. {put,{x,1}}. {put,{x,2}}. return.从上面可以看到,test:t/0已经被编译器处理成{<<1,2,3>>,<<1,2,4>>},故不会再调用append,
而test:t/4是通过参数动态创建的,从中可以看到它调用的函数及执行流程。
最不解的是在Erlang Shell中执行的结果,为什么和预期的相差这么远?
Erlang的二进制append操作过程中,由于ProcBin(见#MARK_C处)所引用的binary在扩展空间时可能会被移动,此时就必段更新ProcBin的引用指针(ProcBin->val),
如果这时有别的ProcBin引用了这个binary,就可能会出问题,这也违反了变量不可变的原则,
所以,当append操作的过程中,如果出中间执行了一些会影响ProcBin的指令,sub binary就会被设置为以后不可再写。
出现以下情况之一,都会被设为不可再写:
当作结果返回
当作消息被发送(PortOrPid ! Bin1)
当作普通变量插入ETS表
当作普通变量进行二进制匹配操作
在Erlang Shell中,测试代码可以写成这样:
Bin0 = <<1>>. Bin1 = <<Bin0/binary,2>>. Bin2 = <<Bin1/binary,3>>. Bin3 = <<Bin1/binary,4>>. {Bin2,Bin3}.这就说明,每一行都像一个函数一样,执行结果将会被返回保存。
例如,在Erlang Shell中,Bin3 = <<Bin1/binary,4>> 这一句的执行过程如下:
先创建一个大小为可以容器Bin1的heap binary:
// 节选自beam_emu.c do_bs_init_bits_known: // 此处省略N行。。。 erts_bin_offset = 0; erts_writable_bin = 0; hb = (ErlHeapBin *) HTOP; HTOP += heap_bin_size(num_bytes); hb->thing_word = header_heap_bin(num_bytes); hb->size = num_bytes; erts_current_bin = (byte *) hb->data; new_binary = make_binary(hb);接着进行append操作,最后读取结果:
// 节选自beam_emu.c do_bs_get_binary_all_reuse_common: orig = mb->orig; sb = (ErlSubBin *) boxed_val(context_to_binary_context); hole_size = 1 + header_arity(sb->thing_word) - ERL_SUB_BIN_SIZE; sb->thing_word = HEADER_SUB_BIN; sb->size = BYTE_OFFSET(size); sb->bitsize = BIT_OFFSET(size); sb->offs = BYTE_OFFSET(offs); sb->bitoffs = BIT_OFFSET(offs); // 设为不可写 sb->is_writable = 0; sb->orig = orig; if (hole_size) { sb[1].thing_word = make_pos_bignum_header(hole_size-1); } // ...由于Erlang Shell中每一行的执行结果都会被返回保存,所以打断了append连续优化的操作,出现了不是我们预期的结果。
小结
在Erlang编程中,我们要了解Binary二进制的创建场情是否会发挥append的优化特性,特别是在网络编程中的收包解包,要充分利用这一特性以提高效率。
编译器会对erl代码进行优化,测试时要注意这种优化是否会影响测试结果。
Erlang Shell中执行代码时,要了解它的执程流程及与文件代码的区别,以免出现莫名的情况。
erl代码可以通过erlc +\'E\' file.erl和erlc +\'S\' file.erl生成代码的扩展文件和指令文件,观察程序执行过程。
相关文章推荐
- Erlang二进制创建的内部机制和优化(一)
- 实例分析Erlang二进制(Binary)匹配的内部机制
- 创建新对象时内部机制
- erlang二进制数据垃圾回收机制
- erlang二进制数据垃圾回收机制
- 什么是数组:从二进制到多重链表深入理解数据组合的内部机制
- SDWebImage的内部原理、实现机制以及优化方式
- JavaScript创建对象的内部机制
- Window内部机制与创建过程
- UITableView创建Cell时的性能优化(cell的重用机制)
- 下一步目标,在erlang上创建多任务多连接基于非阻塞消息机制服务架构
- Openstack针对nova,cinder,glance使用ceph的虚拟机创建机制优化 .
- 实例分析Erlang二进制(Binary)的匹配优化
- Starling学习笔记分享,starling内部渲染机制与性能优化
- Openstack针对nova,cinder,glance使用ceph的虚拟机创建机制优化
- JavaScript 工作机制:V8 引擎内部机制及如何编写优化代码的 5 个诀窍
- Openstack针对nova,cinder,glance使用ceph的虚拟机创建机制优化
- HashMap内部实现机制及优化----第一篇
- MFC类内创建线程,使用内部变量方法
- Erlang语言简明讲义:从创建、编辑一直到运行Erlang语言程序