zoukankan      html  css  js  c++  java
  • 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

    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)
    ",
                     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超出上限的说法,其实是多余的。

  • 相关阅读:
    js获取服务器值以及服务器获取客户端值
    兼容IE Firefox的table自动换行
    sql行转列,列转行
    JS 压缩解压工具
    ASP.NET组织结构图的画法——数据来源读取数据库
    ANGULAR7的应用和跨域问题解决
    Ajax的使用之ScriptManager
    【.NET序列化和反序列化】基本篇
    Web Service的安全访问【SoapHeader身份认证】
    【C#3.0本质论 第一章】C#和.NET Framework概览
  • 原文地址:https://www.cnblogs.com/jluzhsai/p/4546536.html
Copyright © 2011-2022 走看看