关于erlang的binary

[size=x-large]1. binary数据是可以在不同进程间共享的[/size]

当然这些进程都在同一Erlang节点上。

这与普通term不同,后者作为消息在进程间传递时是要在接收进程中做拷贝的(当然atom数据例外,它们也不会做拷贝)。摘一段[url=http://www.erlang.org/doc/efficiency_guide/processes.html#id68101]原文[/url]在这里:
All data in messages between Erlang processes is copied, with the exception of [url=http://www.erlang.org/doc/efficiency_guide/binaryhandling.html#id56197]refc binaries[/url] on the same Erlang node.

bintest是一个察看binary内存地址的小程序(附后),用它验证一下:

1> Bin = <<1,2,3,4,5,6,7,8>>.
<<1,2,3,4,5,6,7,8>>
2> bintest:get_bin_address(B).
"bin: size=8, ptr=0x7ff2e131bc03"
3> P = spawn(fun() -> receive BinMsg -> BinInfo = bintest:get_bin_address(BinMsg), io:format("~s~n", [BinInfo]) end end).
4> P ! B.
bin: size=8, ptr=0x7ff2e131bc03
<<1,2,3,4,5,6,7,8>>


binary在进程间共享带来的问题就是[url=http://erlang.org/pipermail/erlang-questions/2010-October/053753.html]垃圾回收时的麻烦[/url],(可以用引用计数的方式处理?),这是erlang虚拟机实现者的麻烦,有时候也给我们应用开发者带来麻烦。

[size=x-large]2. 模式匹配得到的binary,实际上是匹配目标字节流的一个片断(sub-binary)[/size]


1> Bin = <<1,2,3,4,5,6,7,8>>.
<<1,2,3,4,5,6,7,8>>
2> <<_:3/binary, B:3/binary, _/binary>> = Bin.
<<1,2,3,4,5,6,7,8>>
3> B.
<<4,5,6>>
4> binary:referenced_byte_size(Bin).
8
5> binary:referenced_byte_size(B).
8


我们可能有一个很大的Bin,然后对它进行匹配查找,找到其中一小段B,实际上这两个变量都指向同一个binary(的不同位置和大小)。可以通过binary:referenced_byte_size/1函数察看变量引用背后的二进制数据的实际大小,如上面例子所示。所以除非这两个变量都释放,它们实际引用的那个大binary就不会被垃圾回收。

[url=http://www.erlang.org/doc/man/binary.html#referenced_byte_size-1]binary:referenced_byte_size/1[/url]得到binary变量所引用的原始binary数据的大小。所以模式匹配出来的binary变量还是引用到了原始的数据,没有任何拷贝操作。可以进一步看看binary变量引用的字节流地址:

6> bintest:get_bin_address(Bin).
"bin: size=8, ptr=0x7f9556578c00"
7> bintest:get_bin_address(B).
"bin: size=3, ptr=0x7f9556578c03"
8> C = binary:copy(B).
<<4,5,6>>
9> niftest:get_bin_address(C).
"bin: size=3, ptr=0x7f9556239f78"


有可能出现这种情况:在查找完后原来那个大binary不再有用,有用的是那些查找到的结果,这种情况下那个大binary占着大块内存又用不着,由于有小对象引用都指向它,所以也无法垃圾回收。浪费内存实在可耻。这种情况可以考虑使用binary模块的copy函数,我们用binary:copy/1把那些找到的每个小binary都拷贝一份出来,这样就不再引用到原来的大binary对象了,没有引用的binary就可以被垃圾回收了。


[size=x-large]3. binary与字符串[/size]

在erlang中,binary也用做高效的string,但是上述内存共享办法会给nif之间binary字符串的传递带来问题。要处理的字符串可能来自于一个大binary中的一个片段,我们是无法直接将它作为C语言的字符串处理的,因为C字符串需要一个\0作为字符串的结尾。好在我们知道这个字符串的长度。通过复制添\0的方式可以转换成C能处理的字符串。

这本质上是两种语言内部对binary字符串的表达方式的不同造成的麻烦:一种以\0标志字符串,另一种以长度。

enif_make_string_len可用来处理非\0的C字符串给elang,只要知道长度就行。这其实是erlang的binary字符串处理方式了,这样没有\0结尾的binary字符串也能当成成字符串给erlang用了。

而enif_make_sub_binary是用来在C中模仿erlang的字符串binary操作的。

enif_get_string将erlang字符串(实际上是list,跟binary无关了)转换成\0结尾的C字符串。

而enif_inspect_iolist_as_binary,则是将一个iolist的erlang字符串转换成一个erlang的binary字符串
enif_inspect_iolist_as_binary使用的陷阱是要注意binary字符串是以长度而不是\0标识的。在C中使用使要做转换:

ErlNifBinary tilefilenameBin;
if (!enif_inspect_iolist_as_binary(env, argv[1], &tilefilenameBin) || (tilefilenameBin.size >= 64)) {
return enif_make_badarg(env);
}

char tilefilename[64] = "";
memcpy(tilefilename, tilefilenameBin.data, tilefilenameBin.size);


[size=large]附[/size]

bintest是一个察看binary内存地址的小程序,利用了[url=http://www.erlang.org/doc/man/erl_nif.html#ErlNifBinary]nif的ErlNifBinary结构[/url]:
typedef struct {
unsigned size;
unsigned char* data;
} ErlNifBinary;



-module(bintest).
-export([get_bin_address/1]).

-on_load(init/0).

init() ->
erlang:load_nif("./bintest", 0).

get_bin_address(_Bin) ->
erlang:error({"NIF not implemented in nif_test at line", ?LINE}).


对应的nif C:

#include "erl_nif.h"
#include <stdio.h>

static ERL_NIF_TERM get_bin_address(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
ErlNifBinary bin;
enif_inspect_binary(env, argv[0], &bin);
char buf[256];
sprintf(buf, "bin: size=%zu, ptr=%p", bin.size, bin.data);
return enif_make_string(env, buf, ERL_NIF_LATIN1);
}
static ErlNifFunc nif_funcs[] =
{
{"get_bin_address", 1, get_bin_address}
};

ERL_NIF_INIT(bintest,nif_funcs,NULL,NULL,NULL,NULL);


linux下的编译命令:
gcc -std=c99 -fPIC -shared -o bintest.so bintest.c -I/usr/local/lib/erlang/usr/include
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值