为什么OCaml中的int只有31位?


115

在其他任何地方都没有看到这个“功能”。我知道第32位用于垃圾回收。但是,为什么只对整数而不对其他基本类型使用这种方式呢?


10
请注意,在64位操作系统上,OCaml中的int是63位,而不是31位。这消除了标记位的大多数实际问题(例如,数组大小限制)。如果某些标准算法需要实际的32位整数,那么当然还有int32类型。
Porculus 2010年

1
直到最近,nekoVM(nekovm.org)也具有31位整数。
TheHippo

Answers:


244

这称为标记指针表示,是数十年来在许多不同的解释器,VM和运行时系统中使用的非常常见的优化技巧。几乎每个Lisp实现都使用它们,许多Smalltalk VM,许多Ruby解释器等等。

通常,在这些语言中,您总是传递指向对象的指针。对象本身由对象标头组成,其中包含对象元数据(例如对象的类型,其类),可能包括访问控制限制或安全注释等),然后是实际的对象数据本身。因此,一个简单的整数将表示为一个指针,再加上一个由元数据和实际整数组成的对象。即使使用非常紧凑的表示形式,对于一个简单的整数来说,也大约是6字节。

同样,您不能将这样的整数对象传递给CPU以执行快速整数运算。如果要添加两个整数,则实际上只有两个指针,它们指向要添加的两个整数对象的对象标头的开头。因此,您首先需要对第一个指针执行整数算术,以将偏移量添加到存储整数数据的对象中。然后,您必须取消引用该地址。对第二个整数再次执行相同的操作。现在您有了两个整数,您可以实际要求CPU加。当然,您现在需要构造一个新的整数对象来保存结果。

因此,为了执行一个整数加法,实际上您需要执行三个整数加法,两个指针解引用以及一个对象构造。您将占用近20字节。

但是,诀窍是,所谓的不可变的值类型喜欢整数,你通常不会需要的所有元数据对象中的标头:你可以把所有的东西出来,简单地合成它(这是VM-nerd-当有人在乎时,请说“ fake it”。整数将始终具有class Integer,无需单独存储该信息。如果有人使用反射弄清楚类的整数,你只需回复Integer,没有人会知道,你没有实际上是信息存储在对象头,并在事实上,不是连对象头(或目的)。

因此,关键是要存储值指针内的对象对象,有效地坍塌两成一个。

有些CPU实际上在指针内有额外的空间(所谓的标记位),使您可以在指针本身内存储有关指针的额外信息。诸如“这实际上不是指针,这是一个整数”之类的额外信息。例如Burroughs B5000,各种Lisp机器或AS / 400。不幸的是,当前大多数主流CPU都没有该功能。

但是,有一种解决方法:当地址未在字边界上对齐时,当前大多数主流CPU的工作速度将大大降低。有些甚至根本不支持不对齐的访问。

这意味着在实践中,所有指针都可以被4整除,这意味着它们将始终以两位结尾0。这使我们能够区分实数指针(以结尾00)和实际上是伪装成整数的指针(以结尾1)。而且它仍然使我们拥有所有可以10自由执行其他操作的指针。同样,大多数现代操作系统都为自己保留了非常低的地址,这给了我们另一个麻烦的地方(指针以24 0秒开头,以结束00)。

因此,您可以将31位整数编码为指针,只需将其左移1位并添加1即可。通过简单地适当地移动它们(有时甚至没有必要),就可以对它们执行非常快速的整数运算。

我们如何处理其他地址空间?那么,典型的例子包括编码float在其他大的地址空间和数量之类的特殊对象第truefalsenil,127个ASCII字符,一些常用的短字符串,空列表,空的对象,空数组等附近的0地址。

例如,在MRI,YARV和Rubinius Ruby解释器中,整数是按照我上面所述的方式进行编码的,false被编码为地址0(恰好也是false C 的表示形式)true和地址2(恰好是的C表示true位移了一位)和nilas 4


5
有人说这个答案不准确。我不知道是这种情况还是在挑剔。我只是想指出一些事实,以防万一。
surfmuggle

5
@threeFourOneSixOneThree对于OCaml,此答案并不完全准确,因为在OCaml中,此答案的“合成”部分永远不会发生。OCaml不是像Smalltalk或Java这样的面向对象的语言。从来没有任何理由检索OCaml的方法表int
Pascal Cuoq 2013年

Chrome的V8引擎还使用带标记的指针,并存储一个称为smi(小整数)的31位整数作为优化\
phuclv

@phuclv:这当然不足为奇。就像HotSpot JVM一样,V8基于Animorphic Smalltalk VM,而后者又基于Self VM。而V8是由开发HotSpot JVM,Animorphic Smalltalk VM和Self VM的同一人开发的。特别是Lars Bak,负责所有这些工作,还有他自己的Smalltalk VM,称为OOVM。因此,V8使用Smalltalk世界中的众所周知的技巧一点也不奇怪,因为它是由Smalltalkers基于Smalltalk技术创建的。
约尔格W¯¯米塔格

28

有关详细说明,请参见https://ocaml.org/learn/tutorials/performance_and_profiling.html的“整数,标记位,堆分配的值的表示形式”部分。

简短的答案是,这是为了提高性能。将参数传递给函数时,它可以作为整数或指针传递。在机器级别的语言级别,无法判断寄存器是包含整数还是指针,它只是32位或64位值。因此,OCaml运行时将检查标记位,以确定其接收到的是整数还是指针。如果设置了标记位,则该值为整数,并将其传递给正确的重载。否则,它是一个指针并查找类型。

为什么只有整数才有这个标签?因为其他所有内容都作为指针传递。传递的是整数或指向其他某种数据类型的指针。仅使用一个标记位,就只能有两种情况。


1
“简短的答案是,这是为了表现”。具体来说就是Coq的表现。几乎所有其他内容的性能都受此设计决定的影响。
JD 2015年

17

它不是完全“用于垃圾收集”。它用于内部区分指针和未装箱的整数。


2
随之而来的推论,对于至少一种其他类型,即指针,就是这种方式。如果浮点数也不是31位,那么我认为这是因为它们作为对象存储在堆中,并用指针引用。我想它们的数组有一个紧凑的形式。
Tom Anderson

2
该信息正是GC浏览指针图所需的信息。
东武

“它用于内部区分指针和未装箱的整数”。除GC外,还有其他用途吗?
JD 2015年

13

我必须添加此链接来帮助OP了解更多用于64位OCaml的63位浮点类型

尽管文章的标题看起来差不多float,但它实际上是在讨论。extra 1 bit

OCaml运行时允许通过类型的统一表示来实现多态。每个OCaml值都表示为一个单词,因此可以为例如“事物列表”具有单一实现,并具有访问(例如List.length)和构建(例如List.map)这些列表的功能无论是整数列表,浮点数列表还是整数集列表,其工作原理都相同。

单词中不适合的所有内容都分配在堆中的块中。表示该数据的字就是指向该块的指针。由于堆仅包含单词块,因此所有这些指针都是对齐的:它们的少数最低有效位始终未设置。

无参数的构造函数(例如:类型= fruit = Apple | Orange | Banana)和整数并不代表太多信息,因此需要在堆中分配它们。它们的表示未装箱。数据直接位于单词中,否则该单词将是指针。因此,虽然列表列表实际上是指针列表,但整数列表包含具有较少间接性的整数。由于int和指针的大小相同,因此访问和建立列表的函数不会引起注意。

尽管如此,垃圾收集器仍需要能够从整数识别指针。指针指向堆中格式良好的块,根据定义,该块是活动的(因为GC正在访问该块),因此应进行标记。整数可以具有任何值,并且如果不采取预防措施,它可能偶然看起来像指针。这可能会导致死块看起来还活着,但更糟糕的是,当它实际上跟随一个看起来像指针的整数并弄乱了用户时,它还会导致GC改变它认为是活块标题的位。数据。

这就是为什么未装箱的整数为OCaml程序员提供31位(对于32位OCaml)或63位(对于64位OCaml)的原因。在表示中,在后台,始终设置包含整数的单词的最低有效位,以将其与指针区分开。31位或63位整数非常不常见,因此使用OCaml的任何人都知道这一点。OCaml的用户通常不知道的是为什么64位OCaml没有63位的拆箱浮点类型。


3

为什么OCaml中的int只有31位?

基本上,要在Coq定理证明者上获得最佳性能,其中的主要操作是模式匹配,而主要数据类型是变量类型。发现最佳的数据表示形式是使用标签将指针与未装箱的数据区分开的统一表示形式。

但是,为什么只对整数而不对其他基本类型使用这种方式呢?

不仅如此int。其他类型(例如char和枚举)使用相同的标记表示。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.