web-dev-qa-db-ja.com

OCamlのintが31ビットしかないのはなぜですか?

この「機能」は他のどこでも見たことがない。 32ビット目はガベージコレクションに使用されることを知っています。しかし、なぜ他の基本的なタイプではなく、intに対してのみそのようになっているのでしょうか?

113
Daniel Velkov

これはタグポインター表現と呼ばれ、何十年もの間、さまざまなインタープリター、VM、ランタイムシステムで使用される非常に一般的な最適化の手法です。ほとんどすべてのLISP実装がそれらを使用し、多くのSmalltalk VM、多くのRubyインタープリターなどを使用します。

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

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

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

ただし、トリックとは、いわゆるimmutable value types整数のように、通常はneedオブジェクトヘッダー内のすべてのメタデータを使用しないことです。誰もが見たいときに、それを詰め込み、単純に合成します(これは「偽物」のVMのオタクです)。整数はalwaysクラスIntegerを持つため、その情報を個別に保存する必要はありません。誰かがリフレクションを使用して整数のクラスを把握する場合、単にIntegerと返信するだけで、オブジェクトヘッダーにその情報を実際に保存しなかったこと、そして実際にそこにあることを誰も知らないでしょうisn 'tオブジェクトヘッダー(またはオブジェクト)でも。

そのため、値を保存するのがコツですofポインター内のオブジェクトtoオブジェクト、事実上2つを1つに折りたたみます。

ポインター内に追加のスペースを実際に持つCPU(いわゆるtag bits)があり、ポインター自体にポインターに関する追加情報を保存できます。 「これは実際にはポインタではなく、これは整数です」などの追加情報。例には、バローズB5000、さまざまなLISPマシン、またはAS/400が含まれます。残念ながら、現在のメインストリームCPUのほとんどにはその機能がありません。

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

これが意味するのは、実際にはallポインターは4で割り切れるということです。つまり、それらはalways 2つの0ビットで終わります。これにより、realポインター(00で終わる)と実際には偽装の整数(1で終わる)ポインターを区別できます。そして、10で終わるすべてのポインターが残り、他の処理を自由に行えるようになります。また、ほとんどの最新のオペレーティングシステムは非常に低いアドレスを予約しているため、別の領域を混乱させることができます(たとえば、24 0sで始まり00で終わるポインター)。

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

これらの他のアドレス空間で何をしますか?さて、典型的な例には、他の大きなアドレス空間でのfloatsのエンコードと、truefalsenil、127 ASCIIなどのいくつかの特別なオブジェクト_文字、一般的に使用される短い文字列、空のリスト、空のオブジェクト、空の配列など、0アドレスの近く。

たとえば、MRI、YARV、Rubinius Rubyインタープリターでは、整数は上記の方法でエンコードされます。falseはアドレス0としてエンコードされます(まさにそうですまた Cのfalseの表現)、アドレス2としてのtrue(これはたまたまtrueのC表現になります。ビット)およびnil as 4

242
Jörg W Mittag

適切な説明については、 https://ocaml.org/learn/tutorials/performance_and_profiling.html の「整数、タグビット、ヒープに割り当てられた値の表現」セクションを参照してください。

簡単な答えは、パフォーマンスのためです。関数に引数を渡すとき、整数またはポインターとして渡されます。マシンレベルの言語レベルでは、レジスタに整数またはポインタが含まれているかどうかを判断する方法はありません。それは単なる32ビット値または64ビット値です。そのため、OCamlランタイムはタグビットをチェックして、受け取ったものが整数かポインターかを判断します。タグビットが設定されている場合、値は整数であり、正しいオーバーロードに渡されます。それ以外の場合は、ポインターであり、タイプが検索されます。

なぜ整数だけがこのタグを持っているのですか?他のすべてはポインターとして渡されるためです。渡されるのは、整数または他のデータ型へのポインタです。タグビットが1つだけの場合、2つのケースしかありません。

28
shf301

厳密には「ガベージコレクションに使用される」わけではありません。これは、ポインターとボックス化されていない整数を内部的に区別するために使用されます。

17
Chuck

このリンクを追加して、OPがより多くを理解できるようにする必要があります 64ビットOCamlの63ビット浮動小数点型

記事のタイトルはfloatのように見えますが、実際にはextra 1 bit

OCamlランタイムは、型の統一された表現を通して多態性を可能にします。すべてのOCaml値は単一のWordとして表されるため、これらのリストにアクセス(たとえばList.length)および構築(たとえばList.map)する機能を備えた「モノのリスト」などの単一の実装を持つことが可能です。これらは、intのリスト、floatのリスト、整数のセットのリストのいずれであってもまったく同じように機能します。

Wordに収まらないものはすべて、ヒープ内のブロックに割り当てられます。このデータを表すWordは、ブロックへのポインターになります。ヒープには単語のブロックのみが含まれるため、これらのポインターはすべて揃えられます。それらの少数の最下位ビットは常に設定解除されます。

引数なしのコンストラクター(このような:type fruit = Apple | Orange | Banana)および整数は、ヒープに割り当てる必要があるほど多くの情報を表さない。それらの表現はボックス化されていない。つまり、リストのリストは実際にはポインターのリストですが、intのリストには間接性が1つ少ないintが含まれています。リストにアクセスして作成する関数は、intとポインターのために気付きません。同じサイズを持っています。

それでも、ガベージコレクターは整数からポインターを認識できる必要があります。ポインタは、ヒープ内で(GCがアクセスしているため)定義により生存している適切な形式のブロックを指し、そうマークする必要があります。整数には任意の値を指定できますが、予防策を講じないと、誤ってポインターのように見える可能性があります。これにより、デッドブロックが生きているように見える可能性がありますが、さらに悪いことに、実際にはポインタのように見える整数を追跡してユーザーを混乱させているときに、GCがライブブロックのヘッダーと考えるもののビットを変更しますデータ。

これが、ボックス化されていない整数がOCamlプログラマに31ビット(32ビットOCamlの場合)または63ビット(64ビットOCamlの場合)を提供する理由です。表現では、舞台裏で、整数を含むWordの最下位ビットが常に設定され、ポインターと区別されます。 31ビットまたは63ビットの整数はかなり珍しいため、OCamlを使用している人なら誰でもこれを知っています。 OCamlのユーザーが通常知らないのは、64ビットOCamlに63ビットのボックス化されていないfloat型がない理由です。

13
Jackson Tale

OCamlのintが31ビットしかないのはなぜですか?

基本的に、主要な操作がパターンマッチングであり、主要なデータ型がバリアント型であるCoq定理証明器で可能な限り最高のパフォーマンスを得るために。最良のデータ表現は、タグを使用してポインタをボックス化されていないデータと区別する統一表現であることがわかりました。

しかし、なぜ他の基本的なタイプではなく、intに対してのみそのようになっているのでしょうか?

intだけではありません。 charやenumなどの他のタイプは、同じタグ付き表現を使用します。

3
Jon Harrop