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


115

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


10
64ビットオペレーティングシステムでは、OCamlのintは31ではなく63ビットであることに注意してください。これにより、タグビットのほとんどの実用的な問題(配列サイズの制限など)が取り除かれます。そしてもちろん、標準アルゴリズムに実際の32ビット整数が必要な場合は、int32型があります。
Porculus

1
nekoVM(nekovm.org)も最近まで31ビットintでした。
TheHippo 2013年

回答:


244

これはタグ付きポインター表現と呼ばれ、何十年にもわたって多くの異なるインタープリター、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をtruefalsenil、127 ASCII文字、いくつかの一般的に使用される短い文字列、空のリスト、空のオブジェクト、空の配列などの近くに0住所。

例えば、MRI、YARVとRubiniusのルビー通訳に、整数は、Iは、上述したように符号化されているfalseアドレスとして符号化される0(単によう起こるの表現であることをfalse、C IN)trueアドレスとして2だけそうであることを起こります( C表現は、true1ビットシフト)とnilのように4


5
あり、この答えが不正確であると言う人たちが。これが事実であるのか、それともつまらないのかはわかりません。それが真実を含んでいる場合に備えて、私はそれを指摘するつもりだと思っただけです。
surfmuggle 2013年

5
@threeFourOneSixOneThree OCamlでは、この答えの「それを合成する」部分が実行されないため、この答えはOCamlに対して完全に正確ではありません。OCamlは、SmalltalkやJavaのようなオブジェクト指向言語ではありません。OCamlのメソッドテーブルを取得する理由はありませんint
Pascal Cuoq 2013年

ChromeのV8エンジンもタグ付きポインターを使用し、最適化としてsmi(Small Integer)と呼ばれる31ビット整数を格納します\
phuclv

@phuclv:もちろん、これは当然のことです。HotSpot JVMと同様に、V8はAnimorphic Smalltalk VMに基づいており、これはSelf VMに基づいています。そして、V8は、HotSpot JVM、Animorphic Smalltalk VM、およびSelf VMを開発したのと同じ人々によって開発されました。特にLars Bakはそれらすべてに加えて、OOVMと呼ばれる彼自身のSmalltalk VMに取り組みました。したがって、V8がSmalltalkテクノロジーに基づくSmalltalkerによって作成されたため、V8がSmalltalkの世界でよく知られているトリックを使用していることは驚くにあたりません。
イェルクWミッターク

28

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

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

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


1
「短い答えは、それはパフォーマンスのためである」です。特にCoqのパフォーマンス。他のほとんどすべてのパフォーマンスは、この設計決定の影響を受けます。
JD

17

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


2
そして、それの当然の結果は、それ少なくとも1つの他のタイプ、すなわちポインタのための方法であるということです。フロートも31ビットではない場合、それはヒープにオブジェクトとして格納され、ポインターで参照されているためだと思います。ただし、それらの配列にはコンパクトなフォームがあると思います。
トムアンダーソン

2
その情報は、GCがポインターグラフをナビゲートするために必要なものです。
東武

「ポインターとボックス化されていない整数を内部的に区別するために使用されます」。GC以外にそれを使用するものはありますか?
JD

13

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ビットのボックス化されていない浮動小数点型がない理由です。


3

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

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

しかし、なぜそれがintに対してのみであり、他の基本的な型に対してではないのですか?

だけでなくintcharおよび列挙型などの他のタイプは、同じタグ付き表現を使用します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.