Erlang数据类型的表示和实现(4)——boxed 对象
Boxed 对象
Boxed 对象是比较复杂的对象,在 Erlang 中主标签为 10 的 Eterm 表示一个对 boxed 对象的引用。这个 Eterm 除去标签之后剩下的实际上是一个指针,指向具体的 boxed 对象。如下图所示,boxed 对象由对象头和具体的数据组成,这些字都排布在一起,占用进程栈中的一段连续空间(不像列表那样会分开)。
对象头分为 3 部分:主标签固定为 00(因此也没有 Eterm 以 00 为主标签),然后是 4 个位表示的 header 标签,这个标签表示了这个 boxed 对象的具体类型。接下来剩下的部分就是对象的大小 n,在对象头之后的 n 个字就是这个对象的具体数据。具体数据的格式和意义取决于具体的对象类型。对象头中表示大小的值也称为对象的 arity,目前在 Erlang 虚拟机中规定最多使用 24 位表示这个 n,因此对象最大不超过 \(2^{24} = 16777215\) 个字。
下面列出了 Erlang 虚拟机支持的所有 header 标签:
- 0000:表示元组,高位的 size 即元组中元素个数。
- 0001:内部使用的 binary 匹配状态。
- 001x:大数(bignum),x 为符号位,可以表示任意大的整数,限制取决于内存。
- 0100:本地 ref
- 0101:fun
- 0110:浮点数
- 0111:export 信息
- 1000:refc_binary,引用计数的 binary,即大 binary
- 1001:heap_binary,小 binary,直接放在堆中
- 1010:sub_binary,分离 binary 时产生的子 binary
- 1011:没有使用
- 1100:外部 pid
- 1101:外部 port
- 1110:外部 ref
- 1111:没有使用
以上这些 boxed 对象在 Erlang 虚拟机内都称为 thing。这些“东西”有一些是表示 Erlang 开发者可以直接使用的数据类型,有一些则表示内部数据类型。下面我们来依次了解这些数据类型。
元组
元组很简单,实际上就是一个数组,如下图所示的一个 3 元素的元组:
元组中的元素在内存中依次排开,中间没有间隔,每一个元素都是一个 Eterm,所以元组中的元素可以是任意类型的。和列表一样,如果创建一个元组的时候引用了其他对象,那么这些被引用的对象也是共享的。但是当元组跨越了进程的边界的时候,也会被扁平化。
大整数,浮点数
大整数的符号保存在对象头的标签中,因此大整数对象本身的数据保存的就是大整数的绝对值。由于 Erlang 支持任意大的整数,所以大整数的长度一定是至少 1 个字的。那么大整数是以什么样的格式保存在这些机器字中的呢?假设某个大整数需要用 n 个字表示,而且每一个机器字宽度为 64 位元,那么这个大整数的值为:
\[ S = (2^{64})^{n-1} \times w_{n-1} + (2^{64})^{n-2} \times w_{n-2} + \cdots + (2^{64})^{0} \times w_{0} = \sum_{i=0}^{n-1} (2^{64})^{i} \times w_{i} \]
其中 \(w_{i}\) 表示第 \(i\) 个机器字。可以看出,低地址处的机器字表示大整数中的低位。实际上,就是从高地址到低地址一段一段拼起来。Erlang 虚拟机在操作大整数的时候,计算方法和我们笔算算数是一样的。我们做算数笔算的时候,是一个数字一个数字地挨个计算的,如果碰到进位,则加到更高一位的数字中。Erlang 虚拟机的大数算法在做计算的时候,也是一个数字一个数字地算,只不过刚好使用一个机器字作为我们笔算时的一个数字,在虚拟机的代码中,也是把一个机器字 typedef 为 ErtsDigit。换句话说,我们笔算是在算 10 进制数,而 64 位 Erlang 虚拟机内部则是在进行\(2^{64}\)进制的整数计算。Erlang 大数计算的算法都在 erts/emulator/beam/big.c 文件中,有兴趣的读者可以读一读,虽然原理挺简单,但是里面还是有不少技巧的。比如说 Erlang 虚拟机在进行大数计算之前,要事先为结果分配好空间,分配好空间之后大数对象的大小就固定了,大数算法在计算的过程中将结果填在分配好的空间中。此外还有不少大数到字符串(各种进制的表达)之间的转换,在这种转换过程中需要在两个方向预估对方数据类型所需的空间大小。Erlang 虚拟机采用了信息熵的方法,通过查表的方式查找某个进制下的一个数码(应该是 digit,但是在代码中由于已经用 digit 表示一个固定机器字,所以 big.c 代码中用了 character 这个词表示大整数的一个数码)所需的位元数(即 \(\log_{2}r\),\(r\) 表示进制)。
浮点数就比较简单了。Erlang 中的浮点数是双精度浮点数,实际上就是编译器原生的 double 类型,因此在 64 位的机器上刚好是一个机器字的大小。那么浮点数对象的 header 很简单,除去固定标签之外,arity 值固定为 1。然后后面跟着的一个字大小的数据就是浮点数符合 IEEE 754 规范的表示形式(注4)。
本地 ref
如果是在 32 位机器上,ref 对象表示形式如下所示:
header 里面的 arity 总是设置为 3,后面跟着 3 个 32 位的机器字,其中第 0 个只用了 18 位。这个图也结解释了为什么 ref 的值超过了 \(2^{82}\) 之后就会绕回为 0(18+32+32=82)。
在 64 位的机器上,ref 能表示的数据宽度也是 82 位,如果和 32 位机器不一样的话就不好交换数据了。那么如果在 64 位的机器上,只是简单地拓宽 header 以及后面的 3 个字,那么每个 ref 就要浪费 \(4 \times 3=12\) 个字节的空间,所以在小尾顺序的 64 位机器上,就成下面这样子了:
其中 word 0 依然只使用了 18 位,而且在 header 后面的第一个机器字的低 32 位还保存了一个数字 3,即后面跟着的 32 位元字数。
Erlang 虚拟机通过 3 个全局变量分别表示 word 0、1 和 2 的当前值。调用 BIF make_ref() 的时候,Erlang 虚拟机会创建一个新的 ref。由于全局当前 ref 值是用多个变量表示的,所以 make_ref() 会通过一个自旋锁保护对这些变量的操作,递增全局 ref 的值,然后根据新的 ref 值创建新的 ref 对象并返回对应的 Eterm。递增操作针对 word 0 递增,如果 word 0 超过了 \(2^{18}\),则进位到 word 1,word 1 归零的话则进位到 word 2。
我们打印 ref 的时候,得到的是类似 #Ref<0.0.0.2055> 这样的输出,通过 3 个句点将输出结果分为 4 段。第 1 段,和 pid 和 port 的第一段是一样的,表示节点,在本地节点总是为 0,后面 3 段分别为上面的 word 2、1 和 0。所以 ref 较少的时候前面几段都为 0。
binary
binary 的内容比较繁多,单独放在本系列的第 5 篇了。
外部 pid、port 和 ref
外部 Eterm 是 Erlang 虚拟机的分布式节点之间交换 Eterm 时所用的格式。由于具体涉及到分布式 Erlang 的工作细节,所以打算在专门介绍 Erlang 分布式机制的博文中具体讨论。
fun 和 export
涉及到 Beam 虚拟机的代码格式和工作原理,打算放在专门介绍 Beam 虚拟机的博文中具体讨论。
[注4] 参见 http://en.wikipedia.org/wiki/IEEE_floating_point
Erlang数据类型的表示和实现(4)——boxed 对象的更多相关文章
- Erlang数据类型的表示和实现(5)——binary
binary 是 Erlang 中一个具有特色的数据结构,用于处理大块的“原始的”字节块.如果没有 binary 这种数据类型,在 Erlang 中处理字节流的话可能还需要像列表或元组这样的数据结构. ...
- Erlang数据类型的表示和实现(2)——Eterm 和立即数
Erlang 数据类型的内部表示和实现 Erlang 中的变量在绑定之前是自由的,非绑定变量可以绑定一次任意类型的数据.为了支持这种类型系统,Erlang 虚拟机采用的实现方法是用一个带有标签的机器字 ...
- Erlang数据类型的表示和实现(3)——列表
列表 Erlang 中的列表是通过链表实现的,表示列表的 Eterm 就是这个链表的起点.列表 Eterm 中除去 2 位标签 01 之外,剩下的高 62 位表示指向列表中第一个元素的指针的高 62 ...
- Erlang数据类型的表示和实现(1)——数据类型回顾
本文介绍 Erlang 语言中使用的各种数据类型以及这些数据类型在 Erlang 虚拟机内部的表示和实现.了解数据类型的实现可以帮助大家在实际开发过程中正确选择数据类型,并且可以更好更高效地操作这些数 ...
- 基本数据类型用 == 判断的是值 ,对象用 == 判断的是地址 , 判断值的话用 equals()
基本数据类型用 == 判断的是值 ,对象用 == 判断的是地址 , 判断值的话用 equals() 字符串是String的实例
- JavaScript---js语法,数据类型及方法, 数组及方法,JSON对象及方法,日期Date及方法,正则及方法,数据类型转换,运算符, 控制流程(三元运算),函数(匿名函数,自调用函数)
day46 一丶javascript介绍 JavaScript的基础分为三个 1.ECMAScript:JavaScript的语法标准.包括变量,表达式,运算符,函数,if语句,for语句 ...
- JavaScript 基础(数据类型、函数、流程控制、对象)
一.JavaScript概述 1.1 JavaScript的历史 1992年Nombas开发出C-minus-minus(C--)的嵌入式脚本语言(最初绑定在CEnvi软件中).后将其改名Script ...
- Java中,一切皆是对象!为何数据类型中还分为:基本类型和对象?
Java中一切皆是对象!这句话没错,因为八种基本类型都有对应的包装类(int的包装类是Integer),包装类自然就是对象了. 基本类型一直都是Java语言的一部分,这主要是基于程序性能的考量, 基本 ...
- [Erlang 0114] Erlang Resources 小站 2013年7月~12月资讯合集
Erlang Resources 小站 2013年7月~12月资讯合集,方便检索. 附 2013上半年盘点: Erlang Resources 小站 2013年1月~6月资讯合集 小站地 ...
随机推荐
- Linux的Cgroup
为什么要有cgroup Linux系统中经常有个需求就是希望能限制某个或者某些进程的分配资源.也就是能完成一组容器的概念,在这个容器中,有分配好的特定比例的cpu时间,IO时间,可用内存大小等.于是就 ...
- Ioc原理及常用框架
1 IoC理论的背景 我们都知道,在采用面向对象方法设计的软件系统中,它的底层实现都是由N个对象组成的,所有的对象通过彼此的合作,最终实现系统的业务逻辑. 图1:软件系统中耦合的对象 如果我们 ...
- FXForms,自动生成iOS表单
1.简介 FXForms是一个简单的表单提交框架,他的作者是鼎鼎大名的 Nick Lockwood,你也许听说过他的其他的一些框架,比如 iCarousel. 为什么使用FxForms? 表单处理简单 ...
- U3D自定义Inspector项未触发保存事件的解决方案
1.问题描述与解决方案 1.1.说明 应该只有起步做U3D编辑器插件的部分同行需要了解本文. 该问题源于在做UI插件的时候,发现Inspector面板上手动修改值后,没有触发U3D编辑器本身的修改事件 ...
- BI之SSAS完整实战教程5 -- 详解多维数据集结构
之前简单介绍过多维数据集(Cube)的结构. 原来计划将Cube结构这部分内容打散,在实验中穿插讲解, 考虑到结构之间不同的部分都有联系,如果打散了将反而不好理解,还是直接一次性全部讲完. 本篇我们将 ...
- Access数据库的常用数据类型和alter的用法
一.Access比较常用的数据类型:文本.备注.数字.日期/时间.货币 意思 Sql Access 1)文本 nvarchar(30) ...
- sql 大数据库 插入超时问题解决
解决的办法:1. 不要使用自动收缩, 自动收缩会定期收容数据文件的空闲空间,则处理需要的时候却要扩大数据文件,这两者相反的处理,是冲突的设置方法:ALTER DATABASE MyDataBase S ...
- Unity中启动VS时出现"Visual Studio 2010 Shell 无效的许可证数据"的解决办法
(感觉还是cnblog好一点,刚注册成功赶紧把baidu hi的一篇文章搬过来试试) 一直用着Visual Studio 2013给Unity写代码,安装了SQL Server 2014后,在Unit ...
- 二、SQL语句映射文件(2)增删改查、参数、缓存
//备注:该博客引自:http://limingnihao.iteye.com/blog/106076 2.2 select 一个select 元素非常简单.例如: Xml代码 收藏代码 <!- ...
- NLog在.NET Core Console Apps中的简单应用
什么是NLog? NLog is a free logging platform for .NET with rich log routing and management capabilities. ...