`
844604778
  • 浏览: 550349 次
文章分类
社区版块
存档分类
最新评论

Erlang原子(atom)的内部实现及应用

 
阅读更多
Erlang的原子(atom)在匹配中有着重要作用,它兼顾了可读性和运行效率。 通过atom,可以实现很多灵活高效的应用。

例:动态生成模块名和函数名,动态调用函数。
在很多非FP语言里,我们经常是组合字符串来生成函数名,然后赋给一个变量来达到动态调用函数的目的。
但是在Erlang里,把字符串变量当作函数名来用是不行的。下面就来看Erlang动态调用函数的示例。
-module(test).
-compile(export_all).


%% F = integer()
mytest(Fid, A) ->
    Name = "test" ++ integer_to_list(Fid),
    Fun = list_to_existing_atom(Name),
    test:Fun(A).


test1(A) ->
    {ok, A}.


test2(A) ->
    {ok, A}.


test3(A) ->
    {ok, A}.
运行结果:
Eshell V5.10.2  (abort with ^G)
1> test:mytest(1, "this is test:test1/1").
{ok,"this is test:test1/1"}
2> test:mytest(2, "this is test:test2/1").
{ok,"this is test:test2/1"}
3> test:mytest(3, "this is test:test3/1").
{ok,"this is test:test3/1"}

下面继续看几个例子:
Eshell V5.10.2  (abort with ^G)
1> M = list_to_existing_atom("erlang"). 
erlang
2> F = list_to_existing_atom("localtime").
localtime
3> M:F().
{{2013,7,29},{13,40,41}}
4> MF = list_to_existing_atom("erlang:localtime").
** exception error: bad argument
     in function  list_to_existing_atom/1
        called as list_to_existing_atom("erlang:localtime")
5> MF = list_to_atom("erlang:localtime").         
'erlang:localtime'
6> MF().
** exception error: bad function 'erlang:localtime'

上面通过list_to_existing_atom函数成功读取了erlang和localtime这两个已经存在的atom,而且通过M:F()成功调用了erlang:localtime/0。

但是,list_to_existing_atom("erlang:localtime")却是失败的,它是一个不存在的atom,所以只能用list_to_atom/1,就算是生成了'erlang:localtime'这个atom,通过MF()调用也是失败的。


从上面们可以知道,在Erlang中,已经加载的模块和函数,都有分别导出对应的atom,调用时也是通过atom来中转的。

现在来探索一下list_to_atom函数的底层是怎么实现的。

$ERL_TOP/erts/emulator/beam/bif.c
BIF_RETTYPE list_to_atom_1(BIF_ALIST_1)
{
    Eterm res;
    char *buf = (char *) erts_alloc(ERTS_ALC_T_TMP, MAX_ATOM_CHARACTERS);
    // 把字符串暂存到buf中
    int i = intlist_to_buf(BIF_ARG_1, buf, MAX_ATOM_CHARACTERS);


    if (i < 0) {
        erts_free(ERTS_ALC_T_TMP, (void *) buf);
        i = list_length(BIF_ARG_1);
        if (i > MAX_ATOM_CHARACTERS) {
            BIF_ERROR(BIF_P, SYSTEM_LIMIT);
        }
        BIF_ERROR(BIF_P, BADARG);
    }
    // 通过erts_atom_put保存原子,返回一个索引值(一个整数)
    // 进一步查看erts_atom_put函数会看到:
    //     如果原子已经存在,则直接返回已存在的索引值,
    //     如果不存在则保存并生成索引值
    res = erts_atom_put((byte *) buf, i, ERTS_ATOM_ENC_LATIN1, 1);
    ASSERT(is_atom(res));
    erts_free(ERTS_ALC_T_TMP, (void *) buf);
    BIF_RET(res);
}

从上面可看出,atom在内部使用时,它是一个数字(索引值)。

atom可以看作是给字符串生成了一个ID,内部使用的是ID值,必要时可以取出它的内容(字符串),例如用于打印输出。


接着看一下list_to_atom的逆过程,atom_to_list/1函数的实现:
#define INDEX_PAGE_SHIFT 10
#define INDEX_PAGE_SIZE (1 << INDEX_PAGE_SHIFT)
#define INDEX_PAGE_MASK ((1 << INDEX_PAGE_SHIFT)-1)


ERTS_GLB_INLINE Atom*
atom_tab(Uint i)
{
    // 查找atom
    return (Atom *) erts_index_lookup(&erts_atom_table, i);
}


ERTS_GLB_INLINE IndexSlot*
erts_index_lookup(IndexTable* t, Uint ix)
{
    // 经过两次指针移动定位到目标位置
    return t->seg_table[ix>>INDEX_PAGE_SHIFT][ix&INDEX_PAGE_MASK];
}


/*
 * Atom entry.
 */
typedef struct atom {
    IndexSlot slot;  /* atom的索引值保存在slot->index */
    Sint16 len;      /* atom字符串的长度 */
    Sint16 latin1_chars;
    int ord0;
    byte* name;      /* 原子的名称被保存到这里 */
} Atom;


BIF_RETTYPE atom_to_list_1(BIF_ALIST_1)
{
    Atom* ap;
    Uint num_chars, num_built, num_eaten;
    byte* err_pos;
    Eterm res;
#ifdef DEBUG
    int ares;
#endif


    if (is_not_atom(BIF_ARG_1))
      BIF_ERROR(BIF_P, BADARG);


    /* read data from atom table */
    // 通过atom的索引值找到Atom(结构体)
    ap = atom_tab(atom_val(BIF_ARG_1));
    if (ap->len == 0)
      BIF_RET(NIL);	/* the empty atom */


#ifdef DEBUG
    ares =
#endif
        erts_analyze_utf8(ap->name, ap->len, &err_pos, &num_chars, NULL);
    ASSERT(ares == ERTS_UTF8_OK);


    // 读取atom的名称(ap->name)并生成字符串(list)返回
    res = erts_utf8_to_list(BIF_P, num_chars, ap->name, ap->len, ap->len,
                &num_built, &num_eaten, NIL);
    ASSERT(num_built == num_chars);
    ASSERT(num_eaten == ap->len);
    BIF_RET(res);
}

从上面可看到,atom的存储和查找,是通过index和hash来实现的。

atom更深层次的实现,可参见atom.c、index.c、hash.c及external.c。


小结
$ERL_TOP/erts/emulator/beam/atom.names文件中保存了常用的500多个原子,在Eralng启动初始化时会解析这个文件并导入atom。
Erlang的atom是为匹配(比较)而生的,它的作用就是为了匹配更快捷。
atom数量上限是1M,并且不参与GC,一旦创建将永远保存,一旦数量超出上限,VM很快就会宕掉,所以不可滥用atom。



分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics