SlideShare a Scribd company logo
1 of 23
Download to read offline
Erlang 数据结构模块
 array - 动态数组
 dict - 动态散列表实现的字典
 sets - 动态散列表实现的集合
 queue - 双向队列
 gb_trees - 平衡二叉查找树,可作为有序字典使用
 gb_sets - 平衡二叉查找树实现的有序集合
 orddict - 列表实现的有序字典
 ordsets - 列表实现的有序集合
array
  考虑到函数式语言数据不可变的特性,array 使用基数为 10 的 tuple tree 形式实现,将元素更改发生时需要复制的数据限制在很小的范围内,以便提高效率。
  为了平衡存储空间和动态性,tuple tree 是惰性展开的,仅在某个下标被使用时才会创建出对应的 tuple 结构进行存储,。到 R14B02 版本为止的实现里,tuple tree 都是
  只增大不减小的,对 array 进行 resize/2 操作时仅仅是更新了其 size 属性而已;
  array 的下标是从 0 开始的,同 tuple、list 等结构起始下标不同!
  array 有两种遍历方式:普通遍历和稀疏遍历,区别在于稀疏遍历会跳过所有未定义(取值为默认值)的元素,而普通遍历则不会跳过。
array
  array 内部结构如下图所示:




  其中记录字段含义为:
    size - 当前数组中已被用户存储过数据的元素个数;
    max - 当前数组中能够保存的最大元素个数;
    default - 未存储数据元素的默认值,未指定时默认为 undefined;
    elements - 保存数据的 tuple tree
array
  例子:顺序向一个空 array 的 9、0、100 下标处分别存储 a、b、c 数据,array 结构变化如下:
dict
  dict 基于论文 The Design and Implementation of Dynamic Hashing for Sets and Tables in Icon 论文中提出的动态散列技术构建,通过在散列 bucket 数量同
  存储元素数量之间维持一定比例,实现较高的内存使用率及访问效率。
  同 array 结构采用的策略相似,整个散列 bucket 数组也被划分为若干个固定长度的区段(segments),以便在更新特定 bucket 内容时不需要整体复制数据。目前每个
  segment 的大小设定为 16,是反复测试后选择的最佳值。
  dict 的扩张和紧缩都依赖于一定的元素数量阈值触发,该阈值基于当前活动的 bucket 数量按比例计算得出,目前设定为元素数量超过 bucket_no*5 时触发扩张操作,低于
  bucket_no*3 时触发紧缩操作。可能导致元素数量减少的操作都会在操作完成后尝试紧缩 bucket 区段;可能导致元素数量增加的操作都会在操作进行前尝试扩张 bucket 区
  段;
  bucket 中的多个键值对以无序列表形式存储,对 dict 的访问操作最终都会对特定的 bucket 进行遍历。只读操作的遍历是尾递归形式,bucket 较大时附加开销很小;但更新
  操作的遍历是普通递归形式,附加开销较大,所幸通常情况下 dict 的算法可以保证 bucket 不会很长。
  dict 中的键值对本身以 improper list 形式保存,主要是希望以相同的代码高效处理 K-V(1对1) 和 K-Bag(1对多) 两种数据形式。
dict
  dict 内部结构如下图所示:
dict
  其中字段含义为:
    size - 散列表中已存元素数量,初始为 0;
    n - 散列表中活动 bucket 数量,初始为 16;
    maxn - 散列表中当前允许的最大 bucket 数量,扩张操作需要据此判断是否要增加新的 bucket 区段,初始为 16;
    bso - 动态散列算法中计算一个 bucket 及其伙伴 bucket 的分隔下标,以此为界将 bucket 数组划分为对称的两半,初始为 8;
    exp_size - 触发扩张操作的元素数量阈值,根据活动 bucket 数量和加载因子计算得出,初始为 16*5=80;
    con_size - 触发紧缩操作的元素数量阈值,根据活动 bucket 数量和加载因子计算得出,初始为 16*3=48;
    empty - 用于快速创建新 bucket 区段的区段模板,为 16 元 tuple,每个元素都是空列表;
    segs - 散列 bucket 区段组,其下保存了所有的元素数据;
dict
  紧缩发生条件:
    操作完成后元素数量小于 dict 紧缩阈值 dict.con_size
    满足前者的条件下,dict 活动 bucket 数量 dict.n 大于 16
  紧缩过程:每次紧缩掉一个 bucket,bucket 数量足够少时减半 bucket 区段
    计算出最后一个 bucket(dict.n) X 的伙伴 bucket Y 的位置(dict.n-dict.bso);
    将 X 和 Y 的列表合并,替换掉 Y 列表,并清空 X 列表;
    活动 bucket 数量 dict.n 减 1,同时根据加载因子重新计算紧缩与扩充阈值;
    若活动 bucket 数量等于分隔下标 dict.bso,则将 bucket 区段、最大 bucket 数量 dict.maxn 和分隔下标 dict.bso 都减半。
dict
  扩张发生条件:
    操作进行前后元素数量大于 dict 扩张阈值 dict.exp_size
  扩张过程:每次增加一个 bucket,bucket 数量增长到最大值时倍增 bucket 区段
    若活动 bucket 数量 dict.n 等于最大 bucket 数量 dict.maxn,则将 bucket 区段、最大 bucket 数量 dict.maxn 和分隔下标 dict.bso 都倍增;
    活动 bucket 数量 dict.n 加 1,作为新增 bucket X 的下标,同时根据加载因子重新计算紧缩与扩充阈值;
    计算出新增 bucket X 的伙伴 bucket Y 的位置(dict.n-dict.bso);
    将 Y 的列表重新散列,分配到 X 和 Y 两个 bucket 里;
sets
sets 同 dict 所用实现方法一致,只是存储元素从键值对变为了键本身,这里不再赘述。
queue
  为了实现快速的双向入队、出队操作,queue 基于论文 Purely Functional Data Structures 中描述的算法构建;
  queue 被表示为 2 元 tuple,第一个元素是反向排列的尾部元素列表,第二个元素是正向排列的头部元素列表,二者合在一起就是完整的队列;
  queue 内部结构如下图所示:
orddict
  orddict 用按 key 排序的列表表示字典结构,数据量较大时访问开销很大,只适用于数据量较小的情况;
  例如:向 orddict 顺序插入 z->1、a->2、c->3 后,orddict 结构为:[{a,2}, {c,3}, {z,1}]
ordsets
  ordsets 同 orddict 结构一样,只是存储的是 key 而不是键值对;
  例如:向 ordsets 顺序插入 z、a、c 后,ordsets 结构为:[a, c, z]
gb_trees
  gb_trees 是一种平衡二叉查找树结构,基于论文 General Balanced Trees 中描述的算法构建,它可以在不额外在结点上记录数据的情况下,通过简单的平衡策略达到平均
  状况下接近 AVL 树或红黑树的访问效率;
  原文中每个 GB 树需要记录 2 个全局数据:树中结点个数 |T| 和自上次全局重平衡后的结点删除次数 d(T)。基于这 2 个数据可定义重平衡策略如下:
     发生结点插入时,若新增结点 v 的高度 h(v) 满足条件 h(v) > ceil(c*log[2](|T|+d(T))),则沿插入路径回溯寻找高度最小的结点 u 使得 h(u) > ceil(c*log[2]
     (|u|)),然后对以 u 为根的子树进行局部重平衡操作,最后增加全局计数 |T|;
     发生结点删除时,增加全局计数 d(T),若条件 d(T) >= (2^(b/c)-1)*|T| 满足,则对整个树进行全局重平衡,并重置 d(T) 为 0。
     这里 c、b 为常数且满足条件 c > 1、b > 0,使用这种重平衡策略后树的最大高度为 max(h(T)) = ceil(c*log[2](|T|)+b)
gb_trees
Erlang 的 gb_trees 并没有完全按照论文的方式实现,而是进行了若干折中处理:

  因为结点删除不会增加树的高度,故删除操作不进行自动重平衡,以降低删除开销。在大规模删除结点后,为了让查找效率最优化,开发人员可以显式调用 balance/1 方法对整
  棵树进行重平衡;
  由于不进行自动重平衡,gb_trees 不需要记录删除次数 d(T),只有一个全局数据 |T| 需要记录;
  局部重平衡子树的根结点查找策略从原来的 h(u) > ceil(c*log[2](|u|)) 改为 2^h(u) > |u|^c,以提高计算效率。虽然变换前后的条件不完全相同,但实测差异不大;
gb_trees
  gb_trees 内部结构如下图所示:




  其中字段含义为:
    Size - 树中结点总数 |T|;
    Tree - 保存整棵树结构;
    Key - 结点键;
    Val - 结点值;
    Smaller - 左子树结构,其中所有结点键都小于当前结点键;
    Bigger - 右子树结构,其中所有结点键都大于当前结点键;
gb_trees
  smallest/1、largest/1、is_defined/2、lookup/2、get/2 都是尾递归操作,附加空间开销很低;
  所有对树进行变更的操作都是普通递归操作,数据集较大时附加空间开销很大;
  iterator/1 创建的迭代器是一个列表,记录了树中序遍历调用栈,next/2 只是基于迭代器列表进行简单的弹栈、压栈操作而已;
gb_sets
  gb_sets 同 gb_trees 结构基本一致,只是结点上只存储键,不再存储值。
  gb_sets 内部结构如下图所示:
gb_sets
  gb_sets 进行集合间操作时,总是会将元素数量较少的集合 X 转换为有序列表,并根据另一个集合 Y 的元素数量使用不同的方法进行遍历操作:
     若 |Y| < 10,则集合 Y 也被转换为有序列表,X 和 Y 之间的集合操作通过列表归并进行,结果列表再转换为 gb_sets 结构;
     若 |Y|/|X| < c*log[2](|Y|),则仍然将 Y 转换为有序列表处理,这里 c 设为 1,且因 math 模块中没有以 2 为底的对数函数,条件被改为 |Y| < |X|*c1*ln(|Y|),其中
     c1=c/ln(2)=1.46;
     以上条件都不满足时,保持 Y 为树形结构不变进行遍历;
性能评测

 数据结构 写1w次时间(us) 读1w次时间(us)
  array       5398      2808
  dict       19081      3393
  sets       15267      3009
gb_trees     42864      5061
 gb_sets     46527      4902
 orddict   2812805   1248887
 ordsets   1699518    964396
性能评测

queue头部入队1w次时间(us) queue头部查看1w次时间(us) queue头部出队1w次时间(us)
               940                906               1395
queue尾部入队1w次时间(us) queue尾部查看1w次时间(us) queue尾部出队1w次时间(us)
              1036                809               1169
总结
 要求遍历的顺序性时,尽量避免使用效率低下的 orddict 和 ordsets,用 gb_trees 和 gb_sets 代替它们;
 不要求遍历的顺序性时,尽量使用 dict 和 sets,因它们比 gb_trees 和 gb_sets 的效率高;
 平均来看 queue 的入队出队操作基本上同列表头部操作开销一样,效率很高,只需要双向队列访问模式的地方推荐使用;
Q&A

More Related Content

What's hot (8)

海量数据迁移方案
海量数据迁移方案海量数据迁移方案
海量数据迁移方案
 
DB_Algorithm_and_Data_Structure_About_Sort
DB_Algorithm_and_Data_Structure_About_Sort DB_Algorithm_and_Data_Structure_About_Sort
DB_Algorithm_and_Data_Structure_About_Sort
 
Sql 學習繪本
Sql 學習繪本Sql 學習繪本
Sql 學習繪本
 
Bestfly任务管理系统
Bestfly任务管理系统Bestfly任务管理系统
Bestfly任务管理系统
 
Spark streaming经验介绍
Spark streaming经验介绍Spark streaming经验介绍
Spark streaming经验介绍
 
微博实时搜索
微博实时搜索微博实时搜索
微博实时搜索
 
Hadoop基础及hive入门
Hadoop基础及hive入门Hadoop基础及hive入门
Hadoop基础及hive入门
 
Spark性能调优分享
Spark性能调优分享Spark性能调优分享
Spark性能调优分享
 

Viewers also liked

Introduction to Educational Media Production
Introduction to Educational Media ProductionIntroduction to Educational Media Production
Introduction to Educational Media Production
Rachabodin Suwannakanthi
 
Curriculumlibrary Present
Curriculumlibrary PresentCurriculumlibrary Present
Curriculumlibrary Present
Steve Kashdan
 
The Mall
The MallThe Mall
The Mall
dboling
 
43 Things_Heitman-Russakoff
43 Things_Heitman-Russakoff43 Things_Heitman-Russakoff
43 Things_Heitman-Russakoff
dboling
 
VietRees_Newsletter_27_Tuan3_Thang04
VietRees_Newsletter_27_Tuan3_Thang04VietRees_Newsletter_27_Tuan3_Thang04
VietRees_Newsletter_27_Tuan3_Thang04
internationalvr
 
《2012 年商品說明(不良營商手法)(修訂)條例》研討會 - 香港海關
《2012 年商品說明(不良營商手法)(修訂)條例》研討會 - 香港海關《2012 年商品說明(不良營商手法)(修訂)條例》研討會 - 香港海關
《2012 年商品說明(不良營商手法)(修訂)條例》研討會 - 香港海關
HKAIM
 
VietRees_Newsletter_35_Tuan2_Thang06
VietRees_Newsletter_35_Tuan2_Thang06VietRees_Newsletter_35_Tuan2_Thang06
VietRees_Newsletter_35_Tuan2_Thang06
internationalvr
 
Social Psych And Ethics
Social Psych And EthicsSocial Psych And Ethics
Social Psych And Ethics
Steve Kashdan
 

Viewers also liked (20)

Introduction to Educational Media Production
Introduction to Educational Media ProductionIntroduction to Educational Media Production
Introduction to Educational Media Production
 
Curriculumlibrary Present
Curriculumlibrary PresentCurriculumlibrary Present
Curriculumlibrary Present
 
Tle I And Ii Know More About Self
Tle I And Ii   Know More About SelfTle I And Ii   Know More About Self
Tle I And Ii Know More About Self
 
3D Objects in Wat Makutkasattriyaram's e-Museum: Progress, Experiences, and A...
3D Objects in Wat Makutkasattriyaram's e-Museum: Progress, Experiences, and A...3D Objects in Wat Makutkasattriyaram's e-Museum: Progress, Experiences, and A...
3D Objects in Wat Makutkasattriyaram's e-Museum: Progress, Experiences, and A...
 
Internet 2000
Internet 2000Internet 2000
Internet 2000
 
Personality Traits
Personality TraitsPersonality Traits
Personality Traits
 
Buildng Traffc Smulator(BTS)
Buildng Traffc Smulator(BTS)Buildng Traffc Smulator(BTS)
Buildng Traffc Smulator(BTS)
 
Reduce Health Care Cost by People Maintenance
Reduce Health Care Cost by People MaintenanceReduce Health Care Cost by People Maintenance
Reduce Health Care Cost by People Maintenance
 
Beekman5 std ppt_08
Beekman5 std ppt_08Beekman5 std ppt_08
Beekman5 std ppt_08
 
1 plan financiero proyecto emprendedor (1)
1 plan financiero proyecto emprendedor (1)1 plan financiero proyecto emprendedor (1)
1 plan financiero proyecto emprendedor (1)
 
Como cambiar en 30 dias
Como cambiar en 30 diasComo cambiar en 30 dias
Como cambiar en 30 dias
 
The Mall
The MallThe Mall
The Mall
 
Moral Psychology
Moral PsychologyMoral Psychology
Moral Psychology
 
Final Project
Final ProjectFinal Project
Final Project
 
Benchmark
BenchmarkBenchmark
Benchmark
 
43 Things_Heitman-Russakoff
43 Things_Heitman-Russakoff43 Things_Heitman-Russakoff
43 Things_Heitman-Russakoff
 
VietRees_Newsletter_27_Tuan3_Thang04
VietRees_Newsletter_27_Tuan3_Thang04VietRees_Newsletter_27_Tuan3_Thang04
VietRees_Newsletter_27_Tuan3_Thang04
 
《2012 年商品說明(不良營商手法)(修訂)條例》研討會 - 香港海關
《2012 年商品說明(不良營商手法)(修訂)條例》研討會 - 香港海關《2012 年商品說明(不良營商手法)(修訂)條例》研討會 - 香港海關
《2012 年商品說明(不良營商手法)(修訂)條例》研討會 - 香港海關
 
VietRees_Newsletter_35_Tuan2_Thang06
VietRees_Newsletter_35_Tuan2_Thang06VietRees_Newsletter_35_Tuan2_Thang06
VietRees_Newsletter_35_Tuan2_Thang06
 
Social Psych And Ethics
Social Psych And EthicsSocial Psych And Ethics
Social Psych And Ethics
 

Similar to Erlang抽象数据结构简介

Redis内存存储结构分析
Redis内存存储结构分析Redis内存存储结构分析
Redis内存存储结构分析
锐 张
 
第02章 线性表(java版)
第02章  线性表(java版)第02章  线性表(java版)
第02章 线性表(java版)
Yan Li
 
Lambda演算与邱奇编码
Lambda演算与邱奇编码Lambda演算与邱奇编码
Lambda演算与邱奇编码
Qin Jian
 
Sql Server 高级技巧系列之三整体优化
Sql Server 高级技巧系列之三整体优化Sql Server 高级技巧系列之三整体优化
Sql Server 高级技巧系列之三整体优化
向 翔
 
Tokyo Cabinet
Tokyo CabinetTokyo Cabinet
Tokyo Cabinet
rewinx
 
Tokyo Cabinet Key Value数据库及其扩展应用
Tokyo Cabinet  Key Value数据库及其扩展应用Tokyo Cabinet  Key Value数据库及其扩展应用
Tokyo Cabinet Key Value数据库及其扩展应用
rewinx
 
MySQL源码分析.01.代码结构与基本流程
MySQL源码分析.01.代码结构与基本流程MySQL源码分析.01.代码结构与基本流程
MySQL源码分析.01.代码结构与基本流程
Lixun Peng
 
第3章矩阵及其运算
第3章矩阵及其运算第3章矩阵及其运算
第3章矩阵及其运算
eterou
 

Similar to Erlang抽象数据结构简介 (12)

Optimzing mysql
Optimzing mysqlOptimzing mysql
Optimzing mysql
 
Redis 内存存储结构分析 -wuzhu--20110418
Redis 内存存储结构分析 -wuzhu--20110418Redis 内存存储结构分析 -wuzhu--20110418
Redis 内存存储结构分析 -wuzhu--20110418
 
Redis内存存储结构分析
Redis内存存储结构分析Redis内存存储结构分析
Redis内存存储结构分析
 
5, OCP - oracle storage
5, OCP - oracle storage5, OCP - oracle storage
5, OCP - oracle storage
 
第02章 线性表(java版)
第02章  线性表(java版)第02章  线性表(java版)
第02章 线性表(java版)
 
Lambda演算与邱奇编码
Lambda演算与邱奇编码Lambda演算与邱奇编码
Lambda演算与邱奇编码
 
Sql Server 高级技巧系列之三整体优化
Sql Server 高级技巧系列之三整体优化Sql Server 高级技巧系列之三整体优化
Sql Server 高级技巧系列之三整体优化
 
Tokyo Cabinet
Tokyo CabinetTokyo Cabinet
Tokyo Cabinet
 
Tokyo Cabinet Key Value数据库及其扩展应用
Tokyo Cabinet  Key Value数据库及其扩展应用Tokyo Cabinet  Key Value数据库及其扩展应用
Tokyo Cabinet Key Value数据库及其扩展应用
 
Sql培训 (1)
Sql培训 (1)Sql培训 (1)
Sql培训 (1)
 
MySQL源码分析.01.代码结构与基本流程
MySQL源码分析.01.代码结构与基本流程MySQL源码分析.01.代码结构与基本流程
MySQL源码分析.01.代码结构与基本流程
 
第3章矩阵及其运算
第3章矩阵及其运算第3章矩阵及其运算
第3章矩阵及其运算
 

More from Xiaozhe Wang (6)

Paxos 简介
Paxos 简介Paxos 简介
Paxos 简介
 
C/C++调试、跟踪及性能分析工具综述
C/C++调试、跟踪及性能分析工具综述C/C++调试、跟踪及性能分析工具综述
C/C++调试、跟踪及性能分析工具综述
 
Lua/LuaJIT 字节码浅析
Lua/LuaJIT 字节码浅析Lua/LuaJIT 字节码浅析
Lua/LuaJIT 字节码浅析
 
TIP1 - Overview of C/C++ Debugging/Tracing/Profiling Tools
TIP1 - Overview of C/C++ Debugging/Tracing/Profiling ToolsTIP1 - Overview of C/C++ Debugging/Tracing/Profiling Tools
TIP1 - Overview of C/C++ Debugging/Tracing/Profiling Tools
 
中文编码杂谈
中文编码杂谈中文编码杂谈
中文编码杂谈
 
Lua/PHP哈希碰撞攻击浅析
Lua/PHP哈希碰撞攻击浅析Lua/PHP哈希碰撞攻击浅析
Lua/PHP哈希碰撞攻击浅析
 

Erlang抽象数据结构简介

  • 1. Erlang 数据结构模块 array - 动态数组 dict - 动态散列表实现的字典 sets - 动态散列表实现的集合 queue - 双向队列 gb_trees - 平衡二叉查找树,可作为有序字典使用 gb_sets - 平衡二叉查找树实现的有序集合 orddict - 列表实现的有序字典 ordsets - 列表实现的有序集合
  • 2. array 考虑到函数式语言数据不可变的特性,array 使用基数为 10 的 tuple tree 形式实现,将元素更改发生时需要复制的数据限制在很小的范围内,以便提高效率。 为了平衡存储空间和动态性,tuple tree 是惰性展开的,仅在某个下标被使用时才会创建出对应的 tuple 结构进行存储,。到 R14B02 版本为止的实现里,tuple tree 都是 只增大不减小的,对 array 进行 resize/2 操作时仅仅是更新了其 size 属性而已; array 的下标是从 0 开始的,同 tuple、list 等结构起始下标不同! array 有两种遍历方式:普通遍历和稀疏遍历,区别在于稀疏遍历会跳过所有未定义(取值为默认值)的元素,而普通遍历则不会跳过。
  • 3. array array 内部结构如下图所示: 其中记录字段含义为: size - 当前数组中已被用户存储过数据的元素个数; max - 当前数组中能够保存的最大元素个数; default - 未存储数据元素的默认值,未指定时默认为 undefined; elements - 保存数据的 tuple tree
  • 4. array 例子:顺序向一个空 array 的 9、0、100 下标处分别存储 a、b、c 数据,array 结构变化如下:
  • 5. dict dict 基于论文 The Design and Implementation of Dynamic Hashing for Sets and Tables in Icon 论文中提出的动态散列技术构建,通过在散列 bucket 数量同 存储元素数量之间维持一定比例,实现较高的内存使用率及访问效率。 同 array 结构采用的策略相似,整个散列 bucket 数组也被划分为若干个固定长度的区段(segments),以便在更新特定 bucket 内容时不需要整体复制数据。目前每个 segment 的大小设定为 16,是反复测试后选择的最佳值。 dict 的扩张和紧缩都依赖于一定的元素数量阈值触发,该阈值基于当前活动的 bucket 数量按比例计算得出,目前设定为元素数量超过 bucket_no*5 时触发扩张操作,低于 bucket_no*3 时触发紧缩操作。可能导致元素数量减少的操作都会在操作完成后尝试紧缩 bucket 区段;可能导致元素数量增加的操作都会在操作进行前尝试扩张 bucket 区 段; bucket 中的多个键值对以无序列表形式存储,对 dict 的访问操作最终都会对特定的 bucket 进行遍历。只读操作的遍历是尾递归形式,bucket 较大时附加开销很小;但更新 操作的遍历是普通递归形式,附加开销较大,所幸通常情况下 dict 的算法可以保证 bucket 不会很长。 dict 中的键值对本身以 improper list 形式保存,主要是希望以相同的代码高效处理 K-V(1对1) 和 K-Bag(1对多) 两种数据形式。
  • 6. dict dict 内部结构如下图所示:
  • 7. dict 其中字段含义为: size - 散列表中已存元素数量,初始为 0; n - 散列表中活动 bucket 数量,初始为 16; maxn - 散列表中当前允许的最大 bucket 数量,扩张操作需要据此判断是否要增加新的 bucket 区段,初始为 16; bso - 动态散列算法中计算一个 bucket 及其伙伴 bucket 的分隔下标,以此为界将 bucket 数组划分为对称的两半,初始为 8; exp_size - 触发扩张操作的元素数量阈值,根据活动 bucket 数量和加载因子计算得出,初始为 16*5=80; con_size - 触发紧缩操作的元素数量阈值,根据活动 bucket 数量和加载因子计算得出,初始为 16*3=48; empty - 用于快速创建新 bucket 区段的区段模板,为 16 元 tuple,每个元素都是空列表; segs - 散列 bucket 区段组,其下保存了所有的元素数据;
  • 8. dict 紧缩发生条件: 操作完成后元素数量小于 dict 紧缩阈值 dict.con_size 满足前者的条件下,dict 活动 bucket 数量 dict.n 大于 16 紧缩过程:每次紧缩掉一个 bucket,bucket 数量足够少时减半 bucket 区段 计算出最后一个 bucket(dict.n) X 的伙伴 bucket Y 的位置(dict.n-dict.bso); 将 X 和 Y 的列表合并,替换掉 Y 列表,并清空 X 列表; 活动 bucket 数量 dict.n 减 1,同时根据加载因子重新计算紧缩与扩充阈值; 若活动 bucket 数量等于分隔下标 dict.bso,则将 bucket 区段、最大 bucket 数量 dict.maxn 和分隔下标 dict.bso 都减半。
  • 9. dict 扩张发生条件: 操作进行前后元素数量大于 dict 扩张阈值 dict.exp_size 扩张过程:每次增加一个 bucket,bucket 数量增长到最大值时倍增 bucket 区段 若活动 bucket 数量 dict.n 等于最大 bucket 数量 dict.maxn,则将 bucket 区段、最大 bucket 数量 dict.maxn 和分隔下标 dict.bso 都倍增; 活动 bucket 数量 dict.n 加 1,作为新增 bucket X 的下标,同时根据加载因子重新计算紧缩与扩充阈值; 计算出新增 bucket X 的伙伴 bucket Y 的位置(dict.n-dict.bso); 将 Y 的列表重新散列,分配到 X 和 Y 两个 bucket 里;
  • 10. sets sets 同 dict 所用实现方法一致,只是存储元素从键值对变为了键本身,这里不再赘述。
  • 11. queue 为了实现快速的双向入队、出队操作,queue 基于论文 Purely Functional Data Structures 中描述的算法构建; queue 被表示为 2 元 tuple,第一个元素是反向排列的尾部元素列表,第二个元素是正向排列的头部元素列表,二者合在一起就是完整的队列; queue 内部结构如下图所示:
  • 12. orddict orddict 用按 key 排序的列表表示字典结构,数据量较大时访问开销很大,只适用于数据量较小的情况; 例如:向 orddict 顺序插入 z->1、a->2、c->3 后,orddict 结构为:[{a,2}, {c,3}, {z,1}]
  • 13. ordsets ordsets 同 orddict 结构一样,只是存储的是 key 而不是键值对; 例如:向 ordsets 顺序插入 z、a、c 后,ordsets 结构为:[a, c, z]
  • 14. gb_trees gb_trees 是一种平衡二叉查找树结构,基于论文 General Balanced Trees 中描述的算法构建,它可以在不额外在结点上记录数据的情况下,通过简单的平衡策略达到平均 状况下接近 AVL 树或红黑树的访问效率; 原文中每个 GB 树需要记录 2 个全局数据:树中结点个数 |T| 和自上次全局重平衡后的结点删除次数 d(T)。基于这 2 个数据可定义重平衡策略如下: 发生结点插入时,若新增结点 v 的高度 h(v) 满足条件 h(v) > ceil(c*log[2](|T|+d(T))),则沿插入路径回溯寻找高度最小的结点 u 使得 h(u) > ceil(c*log[2] (|u|)),然后对以 u 为根的子树进行局部重平衡操作,最后增加全局计数 |T|; 发生结点删除时,增加全局计数 d(T),若条件 d(T) >= (2^(b/c)-1)*|T| 满足,则对整个树进行全局重平衡,并重置 d(T) 为 0。 这里 c、b 为常数且满足条件 c > 1、b > 0,使用这种重平衡策略后树的最大高度为 max(h(T)) = ceil(c*log[2](|T|)+b)
  • 15. gb_trees Erlang 的 gb_trees 并没有完全按照论文的方式实现,而是进行了若干折中处理: 因为结点删除不会增加树的高度,故删除操作不进行自动重平衡,以降低删除开销。在大规模删除结点后,为了让查找效率最优化,开发人员可以显式调用 balance/1 方法对整 棵树进行重平衡; 由于不进行自动重平衡,gb_trees 不需要记录删除次数 d(T),只有一个全局数据 |T| 需要记录; 局部重平衡子树的根结点查找策略从原来的 h(u) > ceil(c*log[2](|u|)) 改为 2^h(u) > |u|^c,以提高计算效率。虽然变换前后的条件不完全相同,但实测差异不大;
  • 16. gb_trees gb_trees 内部结构如下图所示: 其中字段含义为: Size - 树中结点总数 |T|; Tree - 保存整棵树结构; Key - 结点键; Val - 结点值; Smaller - 左子树结构,其中所有结点键都小于当前结点键; Bigger - 右子树结构,其中所有结点键都大于当前结点键;
  • 17. gb_trees smallest/1、largest/1、is_defined/2、lookup/2、get/2 都是尾递归操作,附加空间开销很低; 所有对树进行变更的操作都是普通递归操作,数据集较大时附加空间开销很大; iterator/1 创建的迭代器是一个列表,记录了树中序遍历调用栈,next/2 只是基于迭代器列表进行简单的弹栈、压栈操作而已;
  • 18. gb_sets gb_sets 同 gb_trees 结构基本一致,只是结点上只存储键,不再存储值。 gb_sets 内部结构如下图所示:
  • 19. gb_sets gb_sets 进行集合间操作时,总是会将元素数量较少的集合 X 转换为有序列表,并根据另一个集合 Y 的元素数量使用不同的方法进行遍历操作: 若 |Y| < 10,则集合 Y 也被转换为有序列表,X 和 Y 之间的集合操作通过列表归并进行,结果列表再转换为 gb_sets 结构; 若 |Y|/|X| < c*log[2](|Y|),则仍然将 Y 转换为有序列表处理,这里 c 设为 1,且因 math 模块中没有以 2 为底的对数函数,条件被改为 |Y| < |X|*c1*ln(|Y|),其中 c1=c/ln(2)=1.46; 以上条件都不满足时,保持 Y 为树形结构不变进行遍历;
  • 20. 性能评测 数据结构 写1w次时间(us) 读1w次时间(us) array 5398 2808 dict 19081 3393 sets 15267 3009 gb_trees 42864 5061 gb_sets 46527 4902 orddict 2812805 1248887 ordsets 1699518 964396
  • 21. 性能评测 queue头部入队1w次时间(us) queue头部查看1w次时间(us) queue头部出队1w次时间(us) 940 906 1395 queue尾部入队1w次时间(us) queue尾部查看1w次时间(us) queue尾部出队1w次时间(us) 1036 809 1169
  • 22. 总结 要求遍历的顺序性时,尽量避免使用效率低下的 orddict 和 ordsets,用 gb_trees 和 gb_sets 代替它们; 不要求遍历的顺序性时,尽量使用 dict 和 sets,因它们比 gb_trees 和 gb_sets 的效率高; 平均来看 queue 的入队出队操作基本上同列表头部操作开销一样,效率很高,只需要双向队列访问模式的地方推荐使用;
  • 23. Q&A