2

0 の object_id は 1、1 は 3、2 は 5 です。

なぜこのようなパターンになるのでしょうか。object_ids のパターンを作成する Fixnums の背後にあるものは何ですか? 0 の ID が 1、1 の ID が 2、2 の ID が 3 であると予想します。

私は何が欠けていますか?

4

2 に答える 2

7

まず最初に、Ruby 言語仕様が s について保証しているのは、空間内で一意であることだけです。それでおしまい。それらは時間的にも一意ではありません。object_id

そのため、特定のobject_id at the same timeを持つオブジェクトは常に1 つしか存在できませんが、別の時点では、別のオブジェクトにobject_ids を再利用できます。

完全に正確に言えば、Ruby が保証するのは、

  • object_idになりますInteger
  • 2 つのオブジェクトが同時に同じobject_idになることはありません
  • オブジェクトは、object_idその存続期間全体にわたって同じになります

object_idあなたが見ているのは、およびFixnumが YARVでどのように実装されているかの副作用です。これは YARV のプライベートな内部実装の詳細であり、いかなる方法でも保証されていません。他の Ruby 実装では異なる方法で実装されている可能性があり (実際に実装している)、Ruby 実装全体でこれが当てはまるとは限りません。YARV の異なるバージョン間で、または異なるプラットフォーム上の同じバージョンでさえも、真であるとは限りません。

実際、これごく最近変更されたもので、32 ビット プラットフォームと 64 ビット プラットフォームでは異なります。

YARV では、object_id単にオブジェクトのメモリ アドレスを返すように実装されています。それはパズルの 1 ピースです。

ナット、なぜ s のメモリアドレスはFixnumそんなに規則的ですか? 実は、この場合、それらメモリアドレスではありません! YARV は特別なトリックを使用して、一部のオブジェクトをポインターにエンコードします。実際には使用されていないポインターがいくつかあるため、それらを使用して特定のものをエンコードできます。

これはタグ付きポインター表現と呼ばれ、何十年にもわたって多くの異なるインタープリター、VM、およびランタイム システムで使用されている非常に一般的な最適化トリックです。ほぼすべての Lisp 実装で、それら、多くの Smalltalk VM、多くの Ruby インタープリターなどを使用しています。

通常、これらの言語では、常にオブジェクトへのポインターを渡します。オブジェクト自体は、オブジェクト メタデータ (オブジェクトのタイプ、そのクラス、アクセス制御の制限やセキュリティ アノテーションなど) を含むオブジェクト ヘッダーと、実際のオブジェクト データ自体で構成されます。したがって、単純な整数は、ポインターと、メタデータと実際の整数で構成されるオブジェクトとして表されます。非常にコンパクトな表現でも、単純な整数の場合は 6 バイトのようなものです。

また、そのような整数オブジェクトを CPU に渡して、高速な整数演算を実行することはできません。2 つの整数を追加する場合、実際には、追加する 2つの整数オブジェクトのオブジェクト ヘッダーの先頭を指す 2 つのポインターしかありません。したがって、最初のポインターで整数演算を実行して、整数データが​​格納されているオブジェクトにオフセットを追加する必要があります。次に、そのアドレスを逆参照する必要があります。2 番目の整数でもう一度同じことを行います。これで、実際に CPU に追加を要求できる 2 つの整数が得られました。もちろん、結果を保持するために新しい整数オブジェクトを作成する必要があります。

したがって、1 つの整数加算を実行するには、実際には3 つの整数加算と 2 つのポインター逆参照と 1 つのオブジェクト構築を実行する必要があります。そして、ほぼ 20 バイトを使用します。

ただし、秘訣は、整数のようないわゆる不変の値型では、通常、オブジェクト ヘッダーにすべてのメタデータを必要としないことです。すべてのものを残して、単純に合成することができます (これは VM-nerd-誰かが見たいと思っているときは、「偽物」を代弁してください)。fixnum には常にclassFixnumがあり、その情報を個別に保存する必要はありません。誰かがリフレクションを使用して fixnum のクラスを見つけた場合、単に返信するだけFixnumで、その情報を実際にオブジェクト ヘッダーに格納していないこと、実際にオブジェクト ヘッダー (または物体)。

したがって、トリックは、オブジェクトのポインター内にオブジェクトの値格納し、2 つを 1 つに効果的に折りたたむことです。

ポインター内に追加のスペース (いわゆるタグ ビット) を実際に持つ CPU があり、ポインター自体内にポインターに関する追加情報を格納できます。「これは実際にはポインターではなく、整数です」などの追加情報。例としては、Burroughs B5000、さまざまな Lisp マシン、または AS/400 が含まれます。残念ながら、現在の主流の CPU のほとんどにはその機能がありません。

ただし、回避策があります。現在の主流の CPU のほとんどは、アドレスがワード境界に揃えられていない場合、動作が大幅に遅くなります。アライメントされていないアクセスをまったくサポートしていないものもあります。

これが意味することは、実際には、すべてのポインターが 4 (32 ビット システムでは、64 ビット システムでは 8) で割り切れるということです。つまり、常に2 (64 ビット システムでは 3)0ビットで終了します。 . これにより、実際のポインター ( で終わる00) と実際には変装した整数であるポインター ( で終わるもの)を区別することができます110そして、他のことを自由に行うためのすべてのポインターが残っています。また、最新のオペレーティング システムのほとんどは、非常に低いアドレスを自分用に予約しているため、別の領域をいじることができます (たとえば、24 で始まり、で0終わるポインター00)。

したがって、31 ビット (または 63 ビット) の整数をポインターにエンコードするには、1 ビット左にシフトして加算1するだけです。そして、それらを適切にシフトするだけで、非常に高速な整数演算を実行できます (場合によってはそれさえ必要ありません)。

これらの他のアドレス空間をどうするか? まあ、典型的な例にはfloat、他の大きなアドレス空間での s のエンコードとtruefalsenil、 、127 個の ASCII 文字、一般的に使用されるいくつかの短い文字列、空のリスト、空のオブジェクト、空の配列などのような多くの特別なオブジェクトが含まれます。0住所。

YARV では、整数は上で説明した方法でfalseエンコードされ、アドレスとしてエンコードされ0(たまたまCでの の表現でもありfalseます)、trueアドレスとして(たまたま1 ビットシフトされ2た C の表現です)、として。truenil4

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

Fixnums は 1 つの機械語に収まる 63 ビットの整数であり、flonums は 1 つの機械語に収まる 62 ビットFloatの s です。falsenilおよびtrueundefined、実装内でのみ使用され、プログラマーには公開されない値です。

32 ビット プラットフォームではflonums は使用されない (30 ビットFloats を使用しても意味がない) ため、ビット パターンが異なることに注意してください。たとえば、64ビット プラットフォームとnil.object_id4異なり、32 ビット プラットフォーム上にあります。8

それで、あなたはそれを持っています:

  • 特定の小さな整数はポインターとしてエンコードされます
  • ポインターはobject_idsに使用されます

したがって

  • 特定の小さな整数には予測可能なobject_idsがあります
于 2014-09-09T12:38:54.593 に答える
1

Fixnumiの場合、object_id はi * 2 + 1です。

の object_id について0, 2, 4、それらは何ですか? それらは、、、falseルビーです。truenil

于 2014-09-09T02:48:19.673 に答える