erlang中的原子(atom)内部实现[转]

转自: http://www.kongqingquan.com/archives/208#more-208

Erlang中的atom由通用hash表实现,虚拟机中atom最终的用数值表示,对应表中的下标值。本文通过list_to_atom函数的实现,分析atom在虚拟机中的内部实现。先看atom的数据结构:

$OTP/erts/emulator/beam/atom.h
typedef struct atom {
    IndexSlot slot;     /* 必须放到结构体顶部,通用hash表时用到!!! */
    Sint16 len;         /* utf8编码时的长度 */
    Sint16 latin1_chars;     /* 0-255 可latin1编码时的长度 否则为 -1 */
    int ord0;         /* ordinal value of first 3 bytes + 7 bits */
    byte* name;         /* atom 的原始值*/
} Atom;

再看IndexSlot结构体,它是一个通用的结构:

$OTP/erts/emulator/beam/index.h
typedef struct index_slot
{
    HashBucket bucket;    /* 必须放到结构体顶部,通用hash表时用到!!! */
    int index;
} IndexSlot;

HashBucket定义

$OTP/erts/emulator/beam/hash.h
typedef struct hash_bucket
{
    struct hash_bucket* next; /* 指向下一个 hash bucket */
    HashValue hvalue;       /* hash值 */
} HashBucket;

直观表示为:
|———————— Atom ————————|
|—— IndexSlot ——|
|— HashBacket —|
Atom结构体的头部为 IndexSlot, IndexSlot的头部则为 HashBacket。这样定义目的是为方便做指针类型转,指向Atom的指针可以转为 IndexSlot、HashBacket。在虚拟机中index table,hash table都写成为工具函数,给不同的应用。

atom最终保存中erts_atom_table全局变量中,erts_atom_table为IndexTable结构体。IndexTable结构体定义:

$OTP/erts/emulator/beam/index.h
typedef struct index_table
{
    Hash htable;     /* hast表,对象到下标的映射 Mapping obj -> index */
    ErtsAlcType_t type;    //内存分配类型
    int size;     /* 已经分配空间的大小, 为1024的整数倍,size小于limit时,会动态增加 */
    int limit;     /* 列表元素的上限 */
    int entries;     /* 当前的列表元素数量 */
    IndexSlot*** seg_table; /* Mapping index -> obj 二维数组,元素为对象指针*/
} IndexTable;

Hash结构体定义:

$OTP/erts/emulator/beam/hash.h
typedef struct hash
{
    HashFunctions fun;  /* 对应的hash函数,不同的应用有不用的定义 */
    int is_allocated;     /* 0 iff hash structure is on stack or is static */
    ErtsAlcType_t type;    //内存分配类型
    char* name;         /* hash表名称,debug时用到,这里为 atom_table */
    int size;         /* 元素数量,动态增长,具体可看atom.c中h_size_table定义 */
    int size20percent;     /* 20 percent of number of slots */
    int size80percent;     /* 80 percent of number of slots */
    int ix;         /* 表中的数量下标 */
    int used;         /* 已用 slots 数 */
    HashBucket** bucket;     /* 一维数据,存在结构体 HashBucket的单向链表 */
} Hash;
typedef struct hash_functions
{
    H_FUN hash;    //hash函数,对应为 atom_hash
    HCMP_FUN cmp;    //比较函数,对应为 atom_cmp
    HALLOC_FUN alloc;//内存分配函数,对应为atom_alloc,会返回一个atom结构体
    HFREE_FUN free;    //内存释放函数,对应为atom_free
} HashFunctions;

erts_index_table简单结构图:

atom的结构体介绍完,直接看 list_to_atom的bif实现。

$OTP/erts/emulator/beam/bif.c
BIF_RETTYPE list_to_atom_1(BIF_ALIST_1)
{
    Eterm res;

    //把list内容复制到buff中,并返回列表的长度
    char *buf = (char *) erts_alloc(ERTS_ALC_T_TMP, MAX_ATOM_CHARACTERS);
    int i = intlist_to_buf(BIF_ARG_1, buf, MAX_ATOM_CHARACTERS);

    ...
    // ... 合法验证
    ...

    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);
}

再看erts_atom_put的实现,函数会先检查atom是否在index_table中,如果已经存在则返回,否则添加新atom到index_table。

$OTP/erts/emulator/beam/atom.c
Eterm erts_atom_put(const byte *name, int len, ErtsAtomEncoding enc, int trunc)
{
    byte utf8_copy[MAX_ATOM_SZ_FROM_LATIN1];
    const byte *text = name;
    int tlen = len;
    Sint no_latin1_chars;
    Atom a;
    int aix;

    // ... 合法性验证,name中的字符串转换为utf8编码,放到utf8_copy中    

    /* 查看hash table中是否已经存在atom,
     * 如果已经在在,则直接返回
     * atom_hash函数计算atom的hash值只用到len和name
     */
    a.len = tlen;
    a.name = (byte *) text;
    atom_read_lock();
    aix = index_get(&erts_atom_table, (void*) &a);
    atom_read_unlock();
    if (aix >= 0) {
    return make_atom(aix);
    }

    // ... enc 为ERTS_ATOM_ENC_UTF8时,合法性验证

    //把atom加入到 hash表中
    atom_write_lock();
    aix = index_put(&erts_atom_table, (void*) &a);
    atom_write_unlock();
    return make_atom(aix);
}

index_put函数在index.h中定义,这里 tmpl传入类型为 atom

ERTS_GLB_INLINE int index_put(IndexTable* t, void* tmpl)
{
   return index_put_entry(t, tmpl)->index;
}

index_put_entry函数,调用hash_put接口,返回的值为Atom结构体,因为IndexSlot,HashButket 都定义在头部,所以可以对指针类型进程转换。如果在返回的IndexSlot->index不少于0(新atom中,index初始化为-1),则atom已经在hash表中,否则把atom加入seg_table,atom数entries++。

$OTP/erts/emulator/beam/index.c
IndexSlot* index_put_entry(IndexTable* t, void* tmpl)
{
   int ix;
   IndexSlot* p = (IndexSlot*) hash_put(&t->htable, tmpl);
   /*
    *如果在index>=0,则tmpl已经在 hash表中了直接返回
    *新分配的atom p->index = -1
    */

   if (p->index >= 0) {
    return p;
   }

   /*
    *检查index表中的元素是否已满
    *如果已满,则动态增长 size
    *如果index table的元素超出上限 limit,虚拟机退出
    *index table可放元素上限为INDEX_PAGE_SIZE的整数倍,
    *因为 entries >= size时才会进入判断语句,而size是以INDEX_PAGE_SIZE增长的
    */
    ix = t->entries;
   if (ix >= t->size) {
    Uint sz;
    if (ix >= t->limit) {
        /* A core dump is unnecessary */
        erl_exit(ERTS_DUMP_EXIT, "no more index entries in %s (max=%d)\n",
                 t->htable.name, t->limit);
    }
    sz = INDEX_PAGE_SIZE*sizeof(IndexSlot*);
    t->seg_table[ix>>INDEX_PAGE_SHIFT] = erts_alloc(t->type, sz);
    t->size += INDEX_PAGE_SIZE;
   }
   t->entries++;
   p->index = ix;

   //保存指针p,p指向的类型是atom,因为IndexSlot在结构体Atom的头部
   //hash_put返回时可以把指针转义为IndexSlot
   t->seg_table[ix>>INDEX_PAGE_SHIFT][ix&INDEX_PAGE_MASK] = p;
   return p;
}

再看hash_put,上面已经说过,Hash->bucket是个一维数据,元素为单向链表。通过fun.hash得到的相同 hash值的元素将放在同一链表中。当bucket中的slot使用量达到80%时,会重新扩充hash表。

$OTP/erts/emulator/beam/hash.c
void* hash_put(Hash* h, void* tmpl)
{
   HashValue hval = h->fun.hash(tmpl);    // 这里h->fun.hash 为 atom_hash
   int ix = hval % h->size;
   //backet存的是Atom类型,因为HashBucket为其首元素,所以可以直接转换
   HashBucket* b = h->bucket[ix];
   while(b != (HashBucket*) 0) {
      if ((b->hvalue == hval) && (h->fun.cmp(tmpl, (void*)b) == 0))
         return (void*) b;    //tmpl已经在hash表中,直接返回
      b = b->next;
   }

   /*
    * hash表中找不到 tmpl
    * 新建atom,h->fun.alloc = atom_alloc,返回 Atom结构体
    * HashBucket为其首元素,可以直接把atom指针转换为HashBucket
    */

   b = (HashBucket*) h->fun.alloc(tmpl);

   if (h->bucket[ix] == NULL)
      h->used++;
   b->hvalue = hval;
   b->next = h->bucket[ix];
   h->bucket[ix] = b;

   /* 80%时重排hash表,增加size值 */
   if (h->used > h->size80percent)
    rehash(h, 1);
   return (void*) b;
}

由可以看到,atom的值其实是index talbe中的下标,再通过make_atom/1转换得到,它最终是一个整数值。函数make_atom做的工作是向右移位,再在低位中加入atom的标签。如果atom已经在erts_index_table中,则不会再添加新值,直接返回。所以对于list_to_atom之前,要先调用list_to_existing_atom来复用atom来防止atom超出上限的说法,其实是多余的。

时间: 2024-11-10 00:34:41

erlang中的原子(atom)内部实现[转]的相关文章

Erlang中atom的实现[转]

转自: http://www.cnblogs.com/zhengsyao/p/3424539.html 在 Erlang 中,使用 atom 既方便又高效,我们就来看看 atom 是怎么实现的.atom 的 Eterm 除去 6 位的标签之外剩下的部分,就是 atom 在 Erlang 虚拟机中的索引,也就是一个整数值.在 Erlang 中,有关 atom 比较的操作只需要比较两个索引值即可,就是整数操作,因此非常高效.atom 本身是一个字符串,那么 atom 的索引是怎样对应上具体的字符串的

Erlang中的基本元素操作

Erlang shell中,用句号加空格.tab或回车来结束表达式,%表示注释的起点,;隔离子句.模块是.erl 文件,库的头文件.hrl, shell中的编译时c(),外编译命令时erlc, 退出shell用q(),或erlang:halt(). 变量以大写字母开头,且不能重新绑定变量,只能一次性赋值,具有不可变状态. 原子是全局的,不需要宏定义或包含文件,以小写字母开头,还可放在单引号内,是极简表达式. 元组tuple是一些数量固定的项目归组成单一实体{,}, 由于是匿名的,通常在第一个元素

关于Erlang中的behaviour

唔,听说过这四个牛逼渣渣的behaviour:gen_server,gen_fsm,gen_event,supervisor.所以也就更加好奇behaviour的实现. 在解释它是怎么工作的之前,我们可以先看一个具体的实现.这可能会帮助我们理解. 我们先定义一个behaviour: -module(my_behaviour). -export([behaviour_info/1]). -export([start/1, stop/0]). behaviour_info(callbacks) ->

erlang中变量作用域

http://erlangdisplay.iteye.com/blog/315452 erlang中变量只能一次赋值,这么“苛刻“的要求下,更别想拥有全局变量了. 变量只在function中存在,这就是函数编程语言的特色. 对于函数参数对应的变量从执行函数开始,到函数执行结束(从咱们编程者的角度来看的确如此,至于从运行时,GC的角度看,咱们就不深究了). 比如下面的函数: Erlang代码   test1(V) -> io:format("V is:~p~n", [V]), % 

Erlang中的record与宏

http://www.cnblogs.com/me-sa/archive/2011/07/20/erlang0006.html 在Erlang中使用Tuple ,数据项的顺序\数量都是确定的,一旦数据项顺序调整或者增减字段,都容易出现badmatch. 同时一些常量如果硬编码到代码中,一旦数值变化,要想全部可靠的替换成新的数值是一个困难的事情. 这两种数据层面的变化,在Erlang中对应的解决方案是: record  Macro record   在代码中我们创建一个record:   -rec

关于erlang中的进程字典(process dictionary)的新理解及其访问速度 (copy来的)

之前对于erlang的进程字典了解的不够清楚,只是知道put().get()函数,即存值和取值,而每个put.get中都有自己的一对Key--Value(键值对)与之对应.一个Key对应一个Value.在erlang中,启动进程节点之后,进程字典的put.get的值是对缓存的处理,而对数据库的操作,相当于是对硬盘的一个操作,可以理解成是一个数据的备份. 举个简单的例子:在游戏中都有好友操作,启动服务之后,玩家点击添加好友操作,进程的节点已经开启,先从内存中获取玩家的进程字典的Value的值,这里

erlang中字符编码转换(转)

转自:http://www.thinksaas.cn/group/topic/244329/ 功能说明: erlang中对各种语言的编码支持不足,此代码是使用erlang驱动了著名的iconv编码库来对字符进行编码转换处理. 文件说明: iconv_erl.c和iconv.h 是erlang字符编码模块的driver,作用是对iconv进行封装.编译后生成iconv_erl.dll,供iconv.erl使用. iconv_makefile.win32 windows上编译iconv_erl.dl

Erlang中一些错误或者异常的标识

erlang中错误大体分为四种: 1. 编译错误    2. 逻辑错误    3. 运行时错误    4. 用户代码生成的错误 编译错误,主要是编译器检测出的代码语法错误 逻辑错误,是指程序没有完成预期的工作,属于开发人员的问题 运行时错误,是指erlang运行时抛出的错误,比如对非数据类型执行算术运算,erlang运行时会捕获异常,并抛出.在erlang中,这类异常的类型为error 用户自定义错误,是指通过exit/1或者throw/1生成 我们把运行时错误以及用户抛出的错误称为异常(exc

Erlang中频繁发送远程消息要注意的问题

http://avindev.iteye.com/blog/76373 注:这篇文章可能会有争议,欢迎提出意见 在Erlang中,如果要实现两个远程节点之间的通信,就需要通过网络来实现,对于消息发送,是使用TCP.如果要在两个节点间频繁发送消息,比如每秒几百上千条,那样就要注意了. 无论是网游服务器开发的书籍,或是经验老道的工程师,都会告诉你,在发送数据包时,尽可能把小的消息组合为一个比较大的包来发送,毕竟一个TCP包的头也很大,首先是浪费带宽,其次调用底层发送的指令也是有开销的.有工程师告诉我