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超出上限的说法,其实是多余的。
erlang中的原子(atom)内部实现[转]的更多相关文章
- Erlang中atom的实现
Erlang的原子(atom)在匹配中有着重要作用,它兼顾了可读性和运行效率. 通过atom,可以实现很多灵活高效的应用. atom可以看作是给字符串生成了一个ID,内部使用的是ID值,必要时可以取出 ...
- Erlang中atom的实现[转]
转自: http://www.cnblogs.com/zhengsyao/p/3424539.html 在 Erlang 中,使用 atom 既方便又高效,我们就来看看 atom 是怎么实现的.ato ...
- Django中的原子事务相关注意事项
Django中的原子事务支持(transaction.atomic)方式函数装饰器或者with语句,这种方式特别是前者和spring里面的AOP事务支持方式基本等同,当然其实质方式都是原始的try.. ...
- erlang中变量作用域
http://erlangdisplay.iteye.com/blog/315452 _开头(包括_)在erlang可以是表明,这个变量可以存任意东西,就是我们常说的全匹配,_A一般来说就是表明这个东 ...
- Erlang中的record与宏
http://www.cnblogs.com/me-sa/archive/2011/07/20/erlang0006.html 在Erlang中使用Tuple ,数据项的顺序\数量都是确定的,一旦数据 ...
- Erlang中一些错误或者异常的标识
erlang中错误大体分为四种: 1. 编译错误 2. 逻辑错误 3. 运行时错误 4. 用户代码生成的错误 编译错误,主要是编译器检测出的代码语法错误 逻辑错误,是指程序没有完成预 ...
- [erlang 001] erlang中的错误及异常处理
一. erlang中的错误 1. 分类 1) 编译错误:主要是编译器检测出的代码语法错误: 2) 逻辑错误:是指程序没有完成预期的工作,属于开发人员的问题: 3) 运行时错误:是指erlang运行时抛 ...
- Socket的UDP协议在erlang中的实现
现在我们看看UDP协议(User Datagram Protocol,用户数据报协议).使用UDP,互联网上的机器之间可以互相发送小段的数据,叫做数据报.UDP数据报是不可靠的,这意味着如果客户端发送 ...
- erlang中字符编码转换(转)
转自:http://www.thinksaas.cn/group/topic/244329/ 功能说明: erlang中对各种语言的编码支持不足,此代码是使用erlang驱动了著名的iconv编码库来 ...
随机推荐
- Java中的字符集
Java中的字符集 1.字符集概述 字符集是各国家文字与字符编码对照表.字符可以看成是计算机中展示的图案效果,每个字符集都对每一种图案进行编码,有着一对一的对应关系.因此进行字符输出时,都需要指定使用 ...
- vuejs父子组件的数据传递
在vue中,父组件往子组件传递参数都是通过属性的形式来传递的 <div id='root'> <counter :count = '1'></counter> &l ...
- DOM(三):querySelector和querySelectorAll
querySelector()方法querySelector()方法接收一个css选择符,返回与该模式匹配的第一个元素,如果没有找到匹配的元素,返回null. //取得body元素 var body ...
- 通过WEB网管登录
6.1 通过WEB网管登录简介 S5100-SI/EI系列以太网交换机提供内置的WEB Server,用户可以通过WEB网管终端(PC)登录到交换机上,利用内置的WEB Server以WEB方式直观 ...
- memcache 基本操作
输入 telnet localhost 11211 步骤: 1.输入 set hans 0 0 3 回车 2. 输入 123 回车 3. get hans 回车 删除操作,输入 delete h ...
- C语言中%p,%u,%lu都有什么用处
%p表示输出这个指针, %d表示后面的输出类型为有符号的10进制整形, %u表示无符号10进制整型, %lu表示输出无符号长整型整数 (long unsigned)
- C语言中volatile关键字的作用[转]
一.前言 1.编译器优化介绍: 由于内存访问速度远不及CPU处理速度,为提高机器整体性能,在硬件上引入硬件高速缓存Cache,加速对内存的访问.另外在现代CPU中指令的执行并不一定严格按照顺序执行,没 ...
- 一个老鸟发的公司内部整理的 Android 学习路线图 Markdown 版本
jixiaohua发了一篇一个老鸟也发了一份他给公司内部小伙伴整理的路线图.另一份 Android 开发学习路线图.可惜不是MarkDown格式的,所以jixiaohua直接上传的截图,在jixiao ...
- 泛型&&枚举
1.枚举类型 JDk1.5中新增了枚举类型,可以使用该功能取代以往定义常量的方式,同时枚举类型还赋予程序在编译时进行检查的功能. 1.1 使用枚举类型设置常量 以往设置常量,通常将常量放在接口中(fi ...
- Vue项目架构设计与工程化实践
摘自Berwin<Vue项目架构设计与工程化实践>github.com/berwin/Blog/issues/14 1.Vue依赖套件 vuex:项目复杂后,用vuex来管理状态 elem ...