在其他任何地方都没有看过这个“功能”。我知道第32位用于垃圾收集。但是为什么这种方式只适用于整数而不适用于其他基本类型?
答案 0 :(得分:241)
这被称为标记指针表示,并且是几十年来在许多不同的解释器,VM和运行时系统中使用的非常常见的优化技巧。几乎每个Lisp实现都使用它们,许多Smalltalk VM,许多Ruby解释器等等。
通常,在这些语言中,您总是传递指向对象的指针。对象本身由一个对象头组成,它包含对象元数据(如对象的类型,类,可能是访问控制限制或安全注释等),然后是实际的对象数据本身。因此,一个简单的整数将表示为指针加上一个由元数据和实际整数组成的对象。即使有一个非常紧凑的表示,对于一个简单的整数,这就像6字节。
此外,您无法将此类整数对象传递给CPU以执行快速整数运算。如果要添加两个整数,真的只有两个指针,指向要添加的两个整数对象的对象标题的开头。因此,首先需要对第一个指针执行整数运算,以将偏移量添加到存储整数数据的对象中。然后你必须取消引用该地址。使用第二个整数再次执行相同操作。现在你有两个整数,你可以实际要求CPU添加。当然,您现在需要构造一个新的整数对象来保存结果。
因此,为了执行一个整数加法,您实际上需要执行三个整数加法加上两个指针dererefences加上一个对象构造。你占用了近20个字节。
然而,诀窍是所谓的不可变值类型就像整数一样,你通常不会需要对象头中的所有元数据:你可以只是留下所有这些东西,并简单地合成它(这是VM-nerd-代表“伪造它”),当有人关心的时候。一个整数将始终具有类Integer
,不需要单独存储该信息。如果有人使用反射来计算整数的类,你只需回复Integer
,并且没有人会知道你实际上并没有将这些信息存储在对象标题中,事实上, isn' t 甚至是对象标题(或对象)。
因此,诀窍是将对象的 对象存储到对象中,有效地将两者合并为一个。
有些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
(恰好也是也是是C中false
的表示,true
表示地址2
(恰好是true
移位一位的C表示)和nil
为4
。
答案 1 :(得分:28)
请参阅https://ocaml.org/learn/tutorials/performance_and_profiling.html的“整数,标记位,堆分配值的表示”部分以获得详细说明。
简短的回答是它是为了表现。将参数传递给函数时,它或者作为整数或指针传递。在机器级语言级别,无法判断寄存器是包含整数还是指针,它只是32位或64位值。因此,OCaml运行时检查标记位以确定它接收的是整数还是指针。如果设置了标记位,则该值为整数,并传递给正确的重载。否则它是一个指针,并且查找了类型。
为什么只有整数才有这个标签?因为其他所有内容都作为指针传递。传递的是整数或指向其他数据类型的指针。只有一个标记位,只能有两种情况。
答案 2 :(得分:17)
它并不完全“用于垃圾收集”。它用于内部区分指针和未装箱的整数。
答案 3 :(得分:13)
我必须添加此链接以帮助OP了解更多A 63-bit floating-point type for 64-bit OCaml
虽然文章的标题似乎是float
,但它实际上是在讨论extra 1 bit
OCaml运行时允许通过统一的多态性 类型的表示。每个OCaml值都表示为单个值 单词,这样就可以有一个单独的实现,比方说, “事物列表”,具有访问功能(例如List.length)和 构建(例如List.map)这些列表,无论它们是否都有效 是整数,浮点数或整数列表的列表。
任何不适合单词的东西都会被分配到一个单元格中 堆。然后,表示该数据的字是指向该块的指针。 由于堆只包含单词块,所以这些指针都是 对齐:它们的几个最低有效位总是未设置。
无论是否构造函数(如下所示:type fruit = Apple | Orange | 香蕉)和整数并不代表它们的信息 需要在堆中分配。他们的表示没有装箱。该 数据直接在单词内部,否则就是a 指针。因此,虽然列表列表实际上是一个指针列表,但是 ints列表包含一个较少间接的int。该 函数访问和构建列表不会注意到因为int和 指针大小相同。
然而,垃圾收集器需要 能够识别整数指针。指针指向a 堆中格式良好的块,根据定义是活的(因为它是 由GC访问,并应标记为。整数可以有 任何价值,如果不采取预防措施,可能会意外地看 像一个指针。这可能导致死区看起来很活跃,但很多 更糟糕的是,它还会导致GC改变其认为的位 实时块的标题,当它实际上跟随整数时 看起来像一个指针,搞乱了用户数据。
这就是为什么未装箱的整数提供31位(对于32位OCaml)或63位(对于 64位OCaml)到OCaml程序员。在表示中,后面 场景,包含整数的单词的最低位 总是设置,以区别于指针。 31位或63位 整数是相当不寻常的,所以任何使用OCaml的人都知道 这个。 OCaml的用户通常不知道的是为什么没有 用于64位OCaml的63位无盒装浮点类型。
答案 4 :(得分:3)
为什么OCaml中的int只有31位?
基本上,要在Coq定理证明器上获得最佳性能,其中主导操作是模式匹配,而主要数据类型是变体类型。发现最佳数据表示是使用标记来区分指针和未装箱数据的统一表示。
但是为什么这种方式只适用于整数而不适用于其他基本类型?
不仅int
。其他类型如char
和枚举使用相同的标记表示。