object_id 0 为 1,1 为 3,2 为 5。
为什么这个模式是这样的?创建 object_ids 模式的 Fixnums 背后是什么?我希望如果 0 具有 id 1,1 具有 id 2,2 具有 id 3..等等。
我错过了什么?
首先要做的事情: Ruby 语言规范唯一保证object_id
s 是它们在空间中是唯一的。而已。它们甚至在时间上都不是唯一的。
因此,在任何给定时间,只能有一个对象同时具有特定object_id
的,但是,在不同的时间,object_id
s 可能会被不同的对象重用。
完全准确地说:Ruby 保证的是
object_id
将是一个Integer
object_id
的object_id
在其整个生命周期中都将具有相同的您所看到的是在 YARV 中如何实现object_id
和s的副作用。Fixnum
这是 YARV 的私有内部实现细节,不能以任何方式保证。其他 Ruby 实现可能(并且确实)以不同的方式实现它们,因此不能保证跨 Ruby 实现都是如此。它甚至不能保证在不同版本的 YARV 中是正确的,甚至对于不同平台上的相同版本也是如此。
事实上,它最近确实发生了变化,并且在 32 位和 64 位平台之间有所不同。
在 YARV 中,object_id
简单地实现为返回对象的内存地址。这是难题的一部分。
牛逼,为什么s的内存地址Fixnum
这么有规律?好吧,实际上,在这种情况下,它们不是内存地址!YARV 使用一种特殊的技巧将一些对象编码为指针。有些指针实际上并没有被使用,因此您可以使用它们来编码某些内容。
这被称为标记指针表示,并且是几十年来在许多不同的解释器、VM 和运行时系统中使用的一种非常常见的优化技巧。几乎每个 Lisp 实现都使用它们,许多 Smalltalk 虚拟机,许多 Ruby 解释器等等。
通常,在这些语言中,您总是传递指向对象的指针。一个对象本身由一个对象头组成,它包含对象元数据(如对象的类型、它的类、可能访问控制限制或安全注释等),然后是实际的对象数据本身。因此,一个简单的整数将表示为一个指针加上一个由元数据和实际整数组成的对象。即使使用非常紧凑的表示形式,对于一个简单的整数来说,这也类似于 6 字节。
此外,您不能将这样的整数对象传递给 CPU 以执行快速整数运算。如果要添加两个整数,实际上只有两个指针,它们指向要添加的两个整数对象的对象头的开头。因此,您首先需要对第一个指针执行整数运算,以将偏移量添加到存储整数数据的对象中。然后你必须取消引用该地址。对第二个整数再次执行相同操作。现在您有两个整数,您实际上可以要求 CPU 相加。当然,您现在需要构造一个新的整数对象来保存结果。
因此,为了执行一个整数加法,您实际上需要执行三个整数加法加上两个指针解引用加上一个对象构造。你占用了将近 20 个字节。
然而,诀窍在于,对于所谓的不可变值类型,如整数,您通常不需要对象标头中的所有元数据:您可以将所有这些东西都放在一边,然后简单地合成它(这就是 VM-nerd-说“假的”),当有人想看的时候。fixnum 将始终具有 class Fixnum
,无需单独存储该信息。如果有人使用反射来确定某个固定编号的类别,您只需回复Fixnum
,没人会知道您实际上并没有将该信息存储在对象头中,事实上,甚至没有对象头(或目的)。
因此,诀窍是将对象的值存储在指向对象的指针中,从而有效地将两者合二为一。
有些 CPU 实际上在指针内有额外的空间(所谓的标记位),允许您在指针本身内存储有关指针的额外信息。诸如“这实际上不是指针,这是一个整数”之类的额外信息。示例包括 Burroughs B5000、各种 Lisp 机器或 AS/400。不幸的是,目前大多数主流 CPU 都没有该功能。
但是,有一条出路:当地址未在字边界上对齐时,大多数当前主流 CPU 的工作速度会明显变慢。有些甚至根本不支持非对齐访问。
这意味着在实践中,所有指针都可以被 4 整除(在 32 位系统上,在 64 位系统上为 8),这意味着它们总是以两个(在 64 位系统上为三个)0
位结束. 这使我们能够区分真正的指针(以 结尾00
)和实际上伪装成整数的指针(以 结尾1
)。它仍然给我们留下了所有可以10
自由做其他事情的指针。此外,大多数现代操作系统为自己保留了非常低的地址,这给了我们另一个可以处理的地方(例如,以 24 0
s 开头并以 s 结尾的指针00
)。
因此,您可以将 31 位(或 63 位)整数编码为指针,只需将其向左移动 1 位并添加1
即可。您可以通过简单地适当地移动它们(有时甚至没有必要)来对它们执行非常快速的整数运算。
我们如何处理其他地址空间?嗯,典型的例子包括float
在另一个大地址空间中编码s和一些特殊的对象,如,,,,true
127个ASCII字符,一些常用的短字符串,空列表,空对象,空数组等等。地址。false
nil
0
在 YARV 中,整数按照我上面描述的方式编码,false
编码为地址0
(恰好也是C中的表示false
),true
地址2
(恰好是true
移位一位的 C 表示)和nil
作为4
.
在YARV中,以下位模式用于对某些特殊对象进行编码:
xxxx xxxx … xxxx xxx1 Fixnum
xxxx xxxx … xxxx xx10 flonum
0000 0000 … 0000 1100 Symbol
0000 0000 … 0000 0000 false
0000 0000 … 0000 1000 nil
0000 0000 … 0001 0100 true
0000 0000 … 0011 0100 undefined
Fixnum
s 是适合单个机器字的 63 位整数,s 是适合单个机器字flonum
的 62 位Float
s。false
,nil
并且true
是您所期望的,undefined
是一个仅在实现内部使用但不向程序员公开的值。
请注意,在 32 位平台上,flonum
不使用 s (使用 30 位Float
s 没有意义),因此位模式不同。nil.object_id
例如,4
在 32 位平台上,8
不像在 64 位平台上。
所以你有它:
object_id
s所以
object_id
的 s对于 Fixnum i
,object_id 是i * 2 + 1
.
对于 object_id of 0, 2, 4
,它们是什么?它们是false
, true
,nil
在红宝石中。