进程字典的结构:

typedef struct proc_dict {unsigned int sizeMask;   //  掩码,用于计算hash值落到data的索引值unsigned int usedSlots;  //  可以使用的插槽数(不等于已经被使用的插槽数)unsigned int arraySize;  //  data数组的长度unsigned int splitPosition;Uint numElements;        //  已经被使用的插槽数Eterm data[1];
} ProcDict;

put操作的源码:

static Eterm pd_hash_put(Process *p, Eterm id, Eterm value)
{//  id为put(Key,Value)的Key值  value为Value值unsigned int hval;Eterm *hp;Eterm *tp;Eterm *bucket;Eterm tpl;Eterm old;Eterm old_val = am_undefined;Eterm tmp;int needed;int new_key = 1;if (p->dictionary == NULL) {//  当前进程字典为空时,初始化结构//  INITIAL_SIZE 值为8ensure_array_size(&p->dictionary, INITIAL_SIZE);p->dictionary->usedSlots = INITIAL_SIZE;p->dictionary->sizeMask = INITIAL_SIZE*2 - 1;p->dictionary->splitPosition = 0;p->dictionary->numElements = 0;}   //  hval为id转换后,在data中的索引值, 即在data数组中的第几项hval = pd_hash_value(p->dictionary, id);//  ARRAY_GET_PTR宏:p->dictionary->data[hval]bucket = ARRAY_GET_PTR(p->dictionary, hval);//  获取Key需要插入的插槽的当前值old = *bucket;//{Key,Value}构成的tuple需要3个wordneeded = 3;         if (is_boxed(old)) {// 若旧值为box类型,加入{Key,Value}后,要改为list,需要额外4个word(list每一项占2个word)needed += 2+2;  } else if (is_list(old)) {i = 0;// 遍历旧list,看Key是否有旧值// TCAR:取列表当前值; TCDR:取列表下一项地址for(tmp = old; tmp != NIL && !EQ(tuple_val(TCAR(tmp))[1], id); tmp = TCDR(tmp)){ ++i;}if (is_nil(tmp)){// 没有旧值,则将{Key,Value}插在列表头,需要2个wordi = -1;needed += 2;} else {// 有旧值,则旧值前的数据需要复制,所以需要2*(i+1)个wordneeded += 2*(i+1);}}// 判断剩余空间是否足够,不够则触发垃圾回收if (HeapWordsLeft(p) < needed) {Eterm root[3];root[0] = id;root[1] = value;root[2] = old;erts_garbage_collect(p, needed, root, 3);id = root[0];value = root[1];old = root[2];}//  创建{Key,Value}的tuplehp = HeapOnlyAlloc(p, 3);tpl = TUPLE2(hp, id, value);//  核心  更新dictionary逻辑if (is_nil(old)) {// 若无旧数据,直接插入,numElements+1ARRAY_PUT(p->dictionary, hval, tpl);++(p->dictionary->numElements);} else if (is_boxed(old)) {if (EQ(tuple_val(old)[1],id)) {// 若有旧数据,且旧数据的Key和当前Key相等,则替换ARRAY_PUT(p->dictionary, hval, tpl);return tuple_val(old)[2];} else {// 若有旧数据,且旧数据Key不等,则构造列表hp = HeapOnlyAlloc(p, 4);tmp = CONS(hp, old, NIL);hp += 2;++(p->dictionary->numElements);ARRAY_PUT(p->dictionary, hval, CONS(hp, tpl, tmp));hp += 2;}} else if (is_list(old)) {if (i == -1) {// 若旧数据已经是列表,且不存在相同的Key,则将当前{Key,Value}置于列表头hp = HeapOnlyAlloc(p, 2);ARRAY_PUT(p->dictionary, hval, CONS(hp, tpl, old));hp += 2;++(p->dictionary->numElements);} else {// i的值为需要被替换的值,在列表中的序号Eterm nlist;int j;// 申请内存hp = HeapOnlyAlloc(p, (i+1)*2);// 列表遍历到位置i,i后的列表数据是不需要被重建的for (j = 0, nlist = old; j < i; j++, nlist = TCDR(nlist)) {;}// nlist是不需要被重建的部分nlist = TCDR(nlist);// 重建listfor (tmp = old; i-- > 0; tmp = TCDR(tmp)) {nlist = CONS(hp, TCAR(tmp), nlist);hp += 2;}// 将插入的值放到列表头nlist = CONS(hp, tpl, nlist);hp += 2;ARRAY_PUT(p->dictionary, hval, nlist);// 这段逻辑其实可以优化,直接替换不用重建列表应该也是可以的return tuple_val(TCAR(tmp))[2];}} else {erl_exit(1, "Damaged process dictionary found during put/2.");}if (p->dictionary->usedSlots <= p->dictionary->numElements) {//  增长字典的插槽数grow(p);}return am_undefined;
}

进程字典空间增长的实现源码:

static void grow(Process *p)
{unsigned int i,j;unsigned int steps = (p->dictionary->usedSlots / 4) & 0xf;Eterm l1,l2;Eterm l;Eterm *hp;unsigned int pos;unsigned int homeSize;int needed = 0;ProcDict *pd = p->dictionary;if (steps == 0)steps = 1;// 增长后空间超过最大值,则return// MAX_HASH 值为 1342177280if ((MAX_HASH - steps) <= pd->usedSlots) {return;}// 扩充dictionary插槽,扩充规则:使用二分法在 tab 数组中找到第一个大于等于pd->usedSlots + steps的值为新的dictionary->data数组长度// steps为增加的使用插槽数, 使用的插槽数(usedSlots)和dictionary->data数组长度(arraySize)是两个概念, usedSlots =< arraySizeensure_array_size(&p->dictionary, pd->usedSlots + steps);pd = p->dictionary;// usedSlots变长,一些数据放置的插槽可能需要改变// splitPosition为上次分裂位置记录pos = pd->splitPosition;homeSize = pd->usedSlots - pd->splitPosition;for (i = 0; i < steps; ++i) {if (pos == homeSize) {homeSize *= 2;pos = 0;}l = ARRAY_GET(pd, pos);pos++;// 若l值为列表,则需要 2*列表长度的空间 (l中某些值可能会映射到别的插槽,所以l需要分裂)// 若l值不是列表,需要挪位置的话,修改位置就好,不需要申请新的空间if (is_not_tuple(l)) {while (l != NIL) {needed += 2;l = TCDR(l);}}}// 堆区剩余空间不够则进行垃圾回收if (HeapWordsLeft(p) < needed) {BUMP_REDS(p, erts_garbage_collect(p, needed, 0, 0));}homeSize = pd->usedSlots - pd->splitPosition;for (i = 0; i < steps; ++i) {//  此处算法是为了计算哪些位置的数据需要调整插槽/*  我们先看计算插槽的算法: temp = hx & sizeMask; if(temp >= usedSlots) then hx & (sizeMask >> 1) else temp;在sizeMask没有改变的情况下:{分析:    旧规则:temp = hx & sizeMask; if(temp >= usedSlots) then hx & (sizeMask >> 1) else temp;新规则:temp = hx & sizeMask; if(temp >= usedSlots + 1) then hx & (sizeMask >> 1) else temp;只有 temp==usedSlots 时,按新旧规则会映射到不同插槽,所有只有该条件下的插槽需要调整位置}当usedSlots增加时, 所有插槽中只有满足 temp == usedSlots 的需要调整位置,即usedSlots增加前, 插槽为hx & (sizeMask >> 1); usedSlots增加后,插槽需要调整到hx & sizeMask,也就是要将 hx & (sizeMask >> 1) 移动到 hx & sizeMask(== usedSlots),因为 sizeMask 始终保持 pow(2,n)-1 的形式,所以每次usedSlots增长时,需要判断插槽 usedSlots-((sizeMask>>1)+1) 的值是否需要移动至插槽 usedSlots;(举个例子, 当usedSlots由9增加到10时,需要判断插槽1的数据是否需要移动至插槽9。注:插槽从0开始)当usedSlots==sizeMask+1后,sizeMask需要进行调整(左移一位后低位补1):(注:为什么usedSlots==sizeMask+1后才调整sizeMask, 因为插槽是从0开始的,如usedSlots等于16,sizeMask等于15时,哈希值会映射到0到15中,sizeMask是够用的){分析:  条件A:usedSlots==sizeMask+1,旧规则:oldtemp = hx & sizeMask; if(oldtemp >= usedSlots) then hx & (sizeMask >> 1) else oldtemp;在条件A下转换为:oldtemp = hx & sizeMask; return oldtemp;新规则:newtemp = hx & (sizeMask<<1); if(newtemp >= usedSlots + 1) then hx & sizeMask else newtemp;在条件A下转换为:newtemp = hx & (sizeMask+1) + oldtemp; if(newtemp >= usedSlots + 1) then oldtemp else newtemp;当newtemp<usedSlots + 1 时, 旧规则放置于插槽oldtemp位置的数据需要转移到newtemp,hx & (sizeMask+1) + oldtemp < usedSlots + 1 ==> hx & usedSlots + oldtemp < usedSlots + 1推得只有 oldtemp < 1,即oldtemp等于0时,需要调整插槽oldtemp==0时, newtemp取值为 hx & (sizeMask+1) = usedSlots}sizeMask调整时,需要判断 插槽0的数据是否转移到插槽usedSlots(举个例子,当usedSlots由16增加到17时,需要将插槽0的数据,移动至插槽16。)因此可以整理出算法:按上述分析整理出的算法可能会和源码实现有所出入,但整体逻辑是一致的splitPosition其实是可以usedSlots和sizeMask计算出来的[注:usedSlots-((sizeMask>>1)+1)],源码直接使用一个字段记录下来,可以省掉一些计算消耗*/if (pd->splitPosition == homeSize) {homeSize *= 2;pd->sizeMask = homeSize*2 - 1;pd->splitPosition = 0;}pos = pd->splitPosition;++pd->splitPosition;++pd->usedSlots;l = ARRAY_GET(pd, pos);if (is_tuple(l)) {// 旧值为tuple,计算新的位置不等于当前位置if (pd_hash_value(pd, tuple_val(l)[1]) != pos) {// 将l插入到新位置ARRAY_PUT(pd, pos + homeSize, l);// l所属旧位置置空ARRAY_PUT(pd, pos, NIL);}} else {l2 = NIL;l1 = l;// 计算列表长度,申请空间for (j = 0; l1 != NIL; l1 = TCDR(l1))j += 2;hp = HeapOnlyAlloc(p, j);while (l != NIL) {// pos不变的数据添加到l1列表, 需要移动到新位置的数据添加到l2列表if (pd_hash_value(pd, tuple_val(TCAR(l))[1]) == pos)l1 = CONS(hp, TCAR(l), l1);elsel2 = CONS(hp, TCAR(l), l2);hp += 2;l = TCDR(l);}if (l1 != NIL && TCDR(l1) == NIL)l1 = TCAR(l1);if (l2 != NIL && TCDR(l2) == NIL)l2 = TCAR(l2);// l1 和 l2 插入到插槽中ARRAY_PUT(pd, pos, l1);ARRAY_PUT(pd, pos + homeSize, l2);}}
}

tab中的数据为进程字典data数组的长度可能的取值

static unsigned int tab[] = {10UL,20UL,40UL,80UL,160UL,320UL,640UL,1280UL,2560UL,5120UL,10240UL,20480UL,40960UL,81920UL,163840UL,327680UL,655360UL,1310720UL,2621440UL,5242880UL,10485760UL,20971520UL,41943040UL,83886080UL,167772160UL,335544320UL,671088640UL,1342177280UL,2684354560UL
}

总结:
进程初始化时,进程字典可用插槽数为8。当插槽被使用完毕后,设 x = p->dictionary->usedSlots+(p->dictionary->usedSlots / 4) & 0xf,然后从tab数组中找到最小的大于等于x的值,设该值为y,然后扩充进程字典 data字段的长度为y,同时设置可以使用插槽数usedSlots字段为x。

Erlang进程字典底层实现剖析相关推荐

  1. Go语言底层原理剖析

    作者:郑建勋 出版社:电子工业出版社 品牌:博文视点 出版时间:2021-08-01 Go语言底层原理剖析

  2. erlang进程的调度效率

    一.概述 与大多数的进程相反,Erlang中的并发很廉价,派生出一个进程就跟面向对象的语言中分配一个对象的开销差不多. 在启动一个复杂的运算时,启动运算.派生进程以及返回结果后,所有进程神奇的烟消云散 ...

  3. 深入理解Go底层原理剖析 (送书)

    互联网迅猛发展的数十年时间里,不断面领着各种新的场景与挑战,例如大数据.大规模集群计算.更复杂的网络环境.多核处理器引起对于高并发的需求,云计算,上千万行的服务器代码-- 那些成熟但上了年纪的语言没能 ...

  4. 『Go 语言底层原理剖析』文末送书

    互联网迅猛发展的数十年时间里,不断面领着各种新的场景与挑战,例如大数据.大规模集群计算.更复杂的网络环境.多核处理器引起对于高并发的需求,云计算,上千万行的服务器代码-- 那些成熟但上了年纪的语言没能 ...

  5. C语言的底层逻辑剖析函数篇(其二),0基础搞定函数,初识函数递归,超详解

    这里写目录标题 C语言的底层逻辑剖析函数篇(其二),0基础搞定函数,初识函数递归,超详解 开篇语 函数的调用(嵌套调用和链式访问) 1.嵌套调用 2.函数的链式访问 函数的声明和定义 函数声明和定义分 ...

  6. Python字典底层实现原理

    字典是否是有序 在Python3.6之前,字典是无序的,但是Python3.7+,字典是有序的. 在3.6中,字典有序是一个implementation detail,在3.7才正式成为语言特性,因此 ...

  7. linux中线程和进程的区别深度剖析底层实现

    文章目录 前言 Linux中进程和线程的共性 Linux中进程的创建 Linux中线程的实现 总结 前言 在没有仔细了解过Linux的进程和线程实现机制之前,看过很多关于进程和线程的博客,从这些博客中 ...

  8. Erlang 进程创建性能测试

    测试代码来自 Progremming Erlang. Erlang: R13B (erts-5.7.1), 启动参数 +P 5000000 系统: Window XP CPU: E8200 2.66G ...

  9. python字典实现原理_python学习笔记_第7天(字典底层原理+选择结构)

    字典:(拓展–重要)字典核心底层原理 字典对象的核心是散列表,散列表是一个稀疏数组(总是有空白元素的数组),数组的每个单元叫做bucket. 每个bucket 有两部分:一个是键对象的引用,一个是值对 ...

最新文章

  1. python3 判断字符串 是否为字母 数字 浮点数 整数
  2. 浅谈MySQL架构体系
  3. Android实现简单短信发送器
  4. prd移动端通用产品需求文档+Axure高保真app社交订餐通用prd文档+产品业务说明+PRD功能性需求+移动端公工通用模板说明+需求分析+竞品分析+产品结构图+产品业务流程图+产品信息图+餐饮系统
  5. 常用HDFS java API
  6. c++和QT实现俄罗斯方块,使用GraphicsView。
  7. 数据保护条例框架与wik解读 第一章 GDPR 个人数据的控制者和处理者必须采取适当的技术和组织措施以实施数据保护原则。在设计和构建处理个人数据的业务流程时,必须考虑到这些原则,并提供保护数据的
  8. 看图识WAF-搜集常见WAF拦截页面
  9. 历届美国梦之队战斗力汇总:梦一无敌 梦十二平淡
  10. matlab中四元数与三维向量的乘,四元数与三维向量相乘运算法则
  11. bzoj #1854 游戏(二分图匹配)
  12. juns java,Java中的基本數據類型
  13. C++编程第一步:判断一个数字是不是整数
  14. 2017 Multi-University Training Contest - Team 4 :Wavel Sequence
  15. java assist_Java-Javaassist(一)
  16. 从零开始学习大数据系列之Linux-02Vim与Shell script
  17. 二分法查找最多查找几次
  18. 城乡规划一些不错的期刊
  19. hadoop过时了?
  20. python autoit打开软件_Python+AutoIt实现界面工具开发

热门文章

  1. 仁盟养老集团,力推老年智能技术落地工作
  2. 从制造到“智造”,探索制造企业破局之道
  3. 机器视觉检测技术在工业零部件的应用
  4. numpy之argmax、argmin、maximum函数
  5. 这5句话不能乱说,说了容易遭人排…
  6. 计算机一级b需要学哪些内容,计算机一级B考试试题及答案
  7. 产品经理是干什么的?产品经理的工作内容与职责
  8. 李斌胡玮炜退出摩拜股东行列 美团点评王兴穆荣均加入
  9. 脚本录制软件python 按键精灵 tc_键鼠录制工具(KeymouseGo)
  10. 基于stm32单片机RFID门禁刷卡/指纹识别系统