erlang ets源码实现浅析

新建ets时默认配置为:

[set, protected, {keypos,1}, {heir,none}, {write_concurrency,false}, 
{read_concurrency,false}, {decentralized_counters,false}]

先看ets:new时会发生什么,即源码 erl_db.c 中 ets_new_2 函数核心实现为:

DbTable* tb = NULL;  //ets表底层存储数据的结构
meth = &db_hash;  //注, 设置ets为DB_ORDERED_SET类型时,meth 为 &db_catree
tb = (DbTable*) erts_db_alloc(ERTS_ALC_T_DB_TABLE, &init_tb, sizeof(DbTable));
//即 使用erl的 ERTS_ALC_T_DB_TABLE 内存分配器分配内存空间

ets查询逻辑源码为erl_db.c的ets_lookup_2
先根据ets表名或id标识获取tb,其核心逻辑为:

if (is_atom(id)) 
{
    erts_rwmtx_t *mtl;
	struct meta_name_tab_entry* bucket = meta_name_tab_bucket(id,&mtl);
  	//有一个全局的meta_name_tab数组,能根据id O(1) 的时间获取到tb结构
    tb = NULL;
	if (bucket->pu.tb != NULL) {
	    if (is_atom(bucket->u.name_atom)) { 
		if (bucket->u.name_atom == id)
		    tb = bucket->pu.tb;
	    }
	}
}
else
    tb = tid2tab(id);  //同样是O(1)的时间消耗获取到tb

获取存储ets数据的tb结构后,执行查询操作

cret = tb->common.meth->db_get(BIF_P, tb, BIF_ARG_2, &ret);

其中meth为db_hash,可以在 erl_db_hash.c 文件中找到 db_get对应的实现逻辑为:

int db_get_hash(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
{
    DbTableHash *tb = &tbl->hash;
  
    hval = MAKE_HASH(key); //对Key值进行hash运算
    ix = hash_to_ix(tb, hval);//根据hash值获取索引
    b = BUCKET(tb, ix);//根据索引从数据bucket中取值

    while(b != 0) {
        if (has_live_key(tb, b, key, hval)) { //遍历获取到的值,直到取到key对应的值
            *ret = get_term_list(p, tb, key, hval, b, NULL);
	    goto done;
	}
        b = b->next;
    }
    *ret = NIL;
done:
    RUNLOCK_HASH(lck);
    return DB_ERROR_NONE;
}

ets插入逻辑源码为erl_db.c的ets_insert_2
其内容的核心为 erl_db_hash.c 文件中的db_put_hash:

int db_put_hash(DbTable *tbl, Eterm obj, int key_clash_fail, SWord *consumed_reds_p)
{
    DbTableHash *tb = &tbl->hash;
    int ret = DB_ERROR_NONE;
    key = GETKEY(tb, tuple_val(obj));//获取key
    hval = MAKE_HASH(key);//计算hash
    ix = hash_to_ix(tb, hval);//根据hash获取索引
    bp = &BUCKET(tb, ix);//获取该索引下的数据
    b = *bp;
    for (;;) 
		if (b == NULL) {//该索引下没有数据,则新建。否则遍历看有没有重复的key
		    goto Lnew;
		}
		if (has_key(tb,b,key,hval)) {
		    break;
		}
		bp = &b->next;
		b = b->next;
    }
    if (tb->common.status & DB_SET) {
		HashDbTerm* bnext = b->next;
		q = replace_dbterm(tb, b, obj);//SET类型,替换重复key值
		q->next = bnext;
		*bp = q;
		goto Ldone;
    }
    // else if  bag类型的代码
Lnew:
    q = new_dbterm(tb, obj); //该调用会申请obj占用的空间
    q->hvalue = hval;
    q->next = b;
    *bp = q;
    {
        int nitems = &(tb)->common.counters;
		int nactive = &(tb)->nactive;
		if (nitems > GROW_LIMIT(nactive) && !IS_FIXED(tb)) {
	    	grow(tb, nitems);//增长hash table
		}
    }
    return DB_ERROR_NONE;
Ldone:
    return ret;
}

综上我们可以发现ets(非DB_ORDERED_SET类)和进程字典都是基于hash算法进行keyvalue的存储。但是ets为了支持进程共享,所以有很多逻辑是用来进行锁的处理,以及存储时需要现申请keyvalue所用的空间;进程字典在进程堆区够用的情况下,是不需要额外申请空间的,空间不够时则会触发gc。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值