在其他任何地方都没有看到这个“功能”。我知道第32位用于垃圾回收。但是,为什么只对整数而不对其他基本类型使用这种方式呢?
在其他任何地方都没有看到这个“功能”。我知道第32位用于垃圾回收。但是,为什么只对整数而不对其他基本类型使用这种方式呢?
Answers:
这称为标记指针表示,是数十年来在许多不同的解释器,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
在其他大的地址空间和数量之类的特殊对象第true
,false
,nil
,127个ASCII字符,一些常用的短字符串,空列表,空的对象,空数组等附近的0
地址。
例如,在MRI,YARV和Rubinius Ruby解释器中,整数是按照我上面所述的方式进行编码的,false
被编码为地址0
(恰好也是false
C 的表示形式)true
和地址2
(恰好是的C表示true
位移了一位)和nil
as 4
。
int
。
有关详细说明,请参见https://ocaml.org/learn/tutorials/performance_and_profiling.html的“整数,标记位,堆分配的值的表示形式”部分。
简短的答案是,这是为了提高性能。将参数传递给函数时,它可以作为整数或指针传递。在机器级别的语言级别,无法判断寄存器是包含整数还是指针,它只是32位或64位值。因此,OCaml运行时将检查标记位,以确定其接收到的是整数还是指针。如果设置了标记位,则该值为整数,并将其传递给正确的重载。否则,它是一个指针并查找类型。
为什么只有整数才有这个标签?因为其他所有内容都作为指针传递。传递的是整数或指向其他某种数据类型的指针。仅使用一个标记位,就只能有两种情况。
它不是完全“用于垃圾收集”。它用于内部区分指针和未装箱的整数。
我必须添加此链接来帮助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位的拆箱浮点类型。