この「機能」を他のどこにも見たことがない。32番目のビットがガベージコレクションに使用されることを知っています。しかし、なぜそれがintに対してのみであり、他の基本的な型に対してではないのですか?
この「機能」を他のどこにも見たことがない。32番目のビットがガベージコレクションに使用されることを知っています。しかし、なぜそれがintに対してのみであり、他の基本的な型に対してではないのですか?
回答:
これはタグ付きポインター表現と呼ばれ、何十年にもわたって多くの異なるインタープリター、VM、およびランタイムシステムで使用されているかなり一般的な最適化トリックです。ほとんどすべてのLisp実装がそれらを使用し、多くのSmalltalk VM、多くのRubyインタープリターなどを使用します。
通常、これらの言語では、常にオブジェクトへのポインタを渡します。オブジェクト自体は、オブジェクトメタデータ(オブジェクトのタイプ、そのクラス、アクセス制御の制限やセキュリティアノテーションなど)を含むオブジェクトヘッダーと、実際のオブジェクトデータ自体で構成されます。したがって、単純な整数は、ポインタとメタデータと実際の整数で構成されるオブジェクトとして表されます。非常にコンパクトな表現でさえ、それは単純な整数の6バイトのようなものです。
また、このような整数オブジェクトをCPUに渡して高速整数演算を実行することもできません。2つの整数を加算する場合は、実際には2つのポインタしかありん。これは、追加したい2つの整数オブジェクトのオブジェクトヘッダーの先頭を指します。したがって、最初のポインターで整数演算を実行して、オブジェクトにオフセットを追加し、整数データが格納されているオブジェクトに追加する必要があります。次に、そのアドレスを逆参照する必要があります。2番目の整数についても同じことを繰り返します。これで、実際にCPUに追加を要求できる2つの整数ができました。もちろん、結果を保持するには新しい整数オブジェクトを作成する必要があります。
したがって、1つの整数の加算を実行するには、実際には3 つの整数の加算を実行する必要があります。整数の加算と2つのポインタの参照解除と1つのオブジェクト構築ます。そして、あなたはほぼ20バイトを占めます。
ただし、コツは、整数などのいわゆる不変の値型では、通常、オブジェクトヘッダーにすべてのメタデータが必要ないことです。それらすべてを省略して、単に合成することができます(VM-nerd- 「偽物」の場合)、誰かが見たがるとき。整数は常に class を持ちInteger
ます。その情報を個別に保存する必要はありません。誰かがリフレクションを使用して整数のクラスを理解する場合、あなたは単に返信しInteger
、実際にその情報をオブジェクトヘッダーに格納していないこと、そして実際にはオブジェクトヘッダー(またはオブジェクト)。
だから、トリック値格納するのポインタ内のオブジェクトに効果的に一つに二つ崩壊、オブジェクト。
ポインタ(いわゆるタグビット)内に実際に追加のスペースがあるCPUがあります。これにより、ポインタに関する追加情報をポインタ自体に格納できます。「これは実際にはポインタではなく、整数です」などの追加情報。例としては、バローズB5000、さまざまなLispマシン、AS / 400などがあります。残念ながら、現在の主流のCPUのほとんどにはその機能がありません。
ただし、解決策はあります。現在の主流のCPUのほとんどは、アドレスがワード境界に配置されていない場合、動作が大幅に遅くなります。一部では、非境界整列アクセスをまったくサポートしていません。
これが意味することは、実際には、すべてのポインタは4で割り切れるということです。つまり、それらは常に 2 0
ビットで終了します。これにより、実際のポインター(末尾が00
)と、実際には変装した整数のポインター(末尾が)を区別できます1
。そして、それでも、10
他のことをするために自由に終わるすべてのポインタを私たちに残しています。また、最近のほとんどのオペレーティングシステムは、非常に低いアドレスを予約しているため、混乱する別の領域があります(たとえば、24 0
秒で始まり、で終わるポインター00
)。
したがって、31ビットの整数を1ビット左にシフトして追加1
するだけで、ポインターにエンコードできます。そして、それらを適切にシフトするだけで、非常に高速な整数演算を実行できます(必要でない場合もあります)。
これらの他のアドレススペースをどのように処理しますか?さて、一般的な例としては、コード含むfloat
他の大規模なアドレス空間等の特殊オブジェクトの数Sをtrue
、false
、nil
、127 ASCII文字、いくつかの一般的に使用される短い文字列、空のリスト、空のオブジェクト、空の配列などの近くに0
住所。
例えば、MRI、YARVとRubiniusのルビー通訳に、整数は、Iは、上述したように符号化されているfalse
アドレスとして符号化される0
(単によう起こるもの表現であることをfalse
、C IN)true
アドレスとして2
だけそうであることを起こります( C表現は、true
1ビットシフト)とnil
のように4
。
int
。
詳しい説明については、https://ocaml.org/learn/tutorials/performance_and_profiling.htmlの「整数、タグビット、ヒープに割り当てられた値の表現」セクションをご覧ください。
簡単に言えば、パフォーマンスのためです。関数に引数を渡す場合、引数は整数またはポインターとして渡されます。マシンレベルの言語レベルでは、レジスタに整数またはポインタが含まれているかどうかを判別する方法はありません。これは32ビットまたは64ビットの値です。そのため、OCamlランタイムはタグビットをチェックして、受け取ったものが整数かポインタかを判断します。タグビットが設定されている場合、値は整数であり、正しいオーバーロードに渡されます。それ以外の場合はポインタであり、型が検索されます。
整数だけにこのタグがあるのはなぜですか?それ以外はすべてポインタとして渡されるからです。渡されるのは、整数または他のデータ型へのポインターです。タグビットが1つだけの場合、2つのケースしかありません。
「ガベージコレクションに使用」されているわけではありません。ポインターとボックス化されていない整数を内部的に区別するために使用されます。
OPが64ビットOCamlの63ビット浮動小数点型をより理解できるように、このリンクを追加する必要があります
記事のタイトルはについてですがfloat
、実際にはextra 1 bit
OCamlランタイムでは、型の統一表現によるポリモーフィズムが可能です。すべてのOCaml値は単一の単語として表されるため、これらのリストにアクセスして(List.lengthなど)構築し(List.mapなど)構築する関数を使用して、「モノのリスト」などの単一の実装を持つことができます。それらは、整数、浮動小数点数、または整数のセットのリストのリストであっても、まったく同じように機能します。
一言で言えないものは、ヒープ内のブロックに割り当てられます。このデータを表す単語は、ブロックへのポインタです。ヒープには単語のブロックしか含まれていないため、これらのポインターはすべて整列されます。それらの最下位ビットは常に未設定です。
引数なしのコンストラクター(このように:タイプfruit = Apple | Orange | Banana)と整数は、ヒープに割り当てる必要があるほど多くの情報を表していません。それらの表現はボックス化されていません。データは、それ以外の場合はポインタであった単語のすぐ内側にあります。したがって、リストのリストは実際にはポインタのリストですが、intのリストには、1つ少ない間接指定のintが含まれています。リストにアクセスして作成する関数は、intとポインタが同じサイズであるため、気づきません。
それでも、ガベージコレクターは整数からのポインターを認識できる必要があります。ポインターはヒープ内の整形式ブロックを指します。これは、定義上は(GCによってアクセスされているため)生存しており、マークされている必要があります。整数は任意の値を持つことができ、予防策を講じないと、誤ってポインターのように見える可能性があります。これにより、デッドブロックが生きているように見える可能性がありますが、さらに悪いことに、GCが実際にポインタのように見える整数をたどってユーザーをめちゃくちゃにしているときに、ライブブロックのヘッダーであると考えるもののビットを変更する可能性もありますデータ。
これが、ボックス化されていない整数が31ビット(32ビットOCamlの場合)または63ビット(64ビットOCamlの場合)をOCamlプログラマーに提供する理由です。表現では、舞台裏で、整数を含む単語の最下位ビットが常に設定され、ポインタと区別されます。31ビットまたは63ビット整数はかなり珍しいので、OCamlを使用する人は誰でもこれを知っています。OCamlのユーザーが通常知らないのは、64ビットのOCamlに63ビットのボックス化されていない浮動小数点型がない理由です。