在其他任何地方都没见过这个"功能".我知道第32位用于垃圾收集.但是为什么它只是针对整数而不是其他基本类型呢?
Jör*_*tag 242
这被称为标记指针表示,并且是几十年来在许多不同的解释器,VM和运行时系统中使用的非常常见的优化技巧.几乎每个Lisp实现都使用它们,许多Smalltalk VM,许多Ruby解释器等等.
通常,在这些语言中,您总是传递指向对象的指针.对象本身由一个对象头组成,它包含对象元数据(如对象的类型,类,可能是访问控制限制或安全注释等),然后是实际的对象数据本身.因此,一个简单的整数将表示为指针加上一个由元数据和实际整数组成的对象.即使有一个非常紧凑的表示,对于一个简单的整数,这就像6字节.
此外,您无法将此类整数对象传递给CPU以执行快速整数运算.如果要添加两个整数,实际上只有两个指针,指向要添加的两个整数对象的对象标题的开头.因此,首先需要对第一个指针执行整数运算,以将偏移量添加到存储整数数据的对象中.然后你必须取消引用该地址.使用第二个整数再次执行相同操作.现在你有两个整数,你可以实际要求CPU添加.当然,您现在需要构造一个新的整数对象来保存结果.
因此,为了执行一个整数加法,您实际上需要执行三个整数加法加上两个指针dererefences加上一个对象构造.你占用了近20个字节.
但是,诀窍是,所谓的不可变的值类型喜欢整数,你通常不会需要的所有元数据对象中的标头:你可以把所有的东西出来,简单地合成它(这是VM-nerd-当有人关心的时候说"假它".整数将始终具有类Integer,不需要单独存储该信息.如果有人使用反射来计算整数的类,你只需回复Integer,没有人会知道你实际上并没有将这些信息存储在对象头中,事实上,甚至没有一个对象头(或者宾语).
因此,关键是要存储值的指针内的对象到对象,有效地坍塌两成一个.
有些CPU实际上在指针内有额外的空间(所谓的标记位),允许您在指针本身内存储有关指针的额外信息.额外的信息,如"这实际上不是一个指针,这是一个整数".例子包括Burroughs B5000,各种Lisp机器或AS/400.不幸的是,目前大多数主流CPU都没有这个功能.
但是,有一条出路:当地址未在字边界上对齐时,大多数当前的主流CPU工作速度明显变慢.有些甚至根本不支持未对齐访问.
这意味着在实践中,所有指针都可以被4整除,这意味着它们总是以两位结束0.这允许我们区分真实的指针(结尾00)和伪装的实际整数的指针(结尾的指针1).它仍然留给我们所有指针,以10自由结束做其他的东西.此外,大多数现代操作系统为自己保留了非常低的地址,这为我们提供了另一个混乱的区域(以24 0秒开头并结束的指针00).
因此,您可以将31位整数编码为指针,只需将其向左移1位并添加1即可.你可以通过简单地移动它们来执行非常快速的整数运算(有时甚至不需要).
我们如何处理其他地址空间?那么,典型的例子包括编码float在其他大的地址空间S和数量之类的特殊对象true,false,nil,127个ASCII字符,一些常用的短字符串,空列表,空的对象,空数组等附近的0地址.
例如,在MRI,YARV和Rubinius的Ruby解释,整数被编码的I上述方式,false被编码为地址0(这恰好也成为的表示false在C)中,true作为地址2(这恰好是C表示true移位一位)和nilas 4.
shf*_*301 28
有关详细说明,请参阅https://ocaml.org/learn/tutorials/performance_and_profiling.html中的"整数,标记位,堆分配值的表示"部分.
简短的回答是它是为了表现.将参数传递给函数时,它或者作为整数或指针传递.在机器级语言级别,无法判断寄存器是包含整数还是指针,它只是32位或64位值.因此,OCaml运行时检查标记位以确定它接收的是整数还是指针.如果设置了标记位,则该值为整数,并传递给正确的重载.否则它是一个指针和类型被查找.
为什么只有整数才有这个标签?因为其他所有内容都作为指针传递.传递的是整数或指向其他数据类型的指针.只有一个标记位,只能有两种情况.
Chu*_*uck 17
它并不完全"用于垃圾收集".它用于内部区分指针和未装箱的整数.
Jac*_*ale 13
我必须添加此链接以帮助OP了解更多64位OCaml的63位浮点类型
虽然文章的标题似乎有关float,但它实际上是在讨论extra 1 bit
OCaml运行时通过类型的统一表示允许多态.每个OCaml值都表示为一个单词,因此可以为"事物列表"提供单个实现,具有访问功能(例如List.length)和构建(例如List.map)这些列表它们的工作方式是相同的,无论它们是整数,浮点数还是整数列表.
任何不适合单词的东西都会在堆中的块中分配.然后,表示该数据的字是指向该块的指针.由于堆只包含单词块,因此所有这些指针都是对齐的:它们的少数最低有效位始终未设置.
无参数构造函数(如:type fruit = Apple | Orange | Banana)和整数不代表需要在堆中分配的信息.他们的表示没有装箱.数据直接位于单词内,否则将成为指针.因此,虽然列表列表实际上是一个指针列表,但是一个int列表包含一个较少间接的int.访问和构建列表的函数没有注意到,因为int和指针具有相同的大小.
但是,垃圾收集器需要能够识别来自整数的指针.指针指向堆中格式正确的块,根据定义它是活动的(因为GC正在访问它)并且应该标记为.整数可以有任何值,如果不采取预防措施,可能会意外地看起来像指针.这可能会导致死区看起来活着,但更糟糕的是,当它实际上跟随一个看起来像指针并弄乱用户的整数时,它也会导致GC改变它认为是活块的标题的位.数据.
这就是为什么未装箱的整数为OCaml编程器提供31位(对于32位OCaml)或63位(对于64位OCaml).在表示中,在幕后,始终设置包含整数的单词的最低有效位,以将其与指针区分开.31位或63位整数相当不寻常,所以任何使用OCaml的人都知道这一点.OCaml的用户通常不知道为什么64位OCaml没有63位未装箱的浮点类型.