ガベージコレクションでハッシュテーブルを使用すると、マークアンドスイープの世界的な問題を解決できますか?


13

mark-sweep-compactガベージコレクションアルゴリズムでは、参照グラフの一貫性が失われ、オブジェクトを指すすべての参照の値を置き換える必要があるため、オブジェクトを再配置する際に世界を停止する必要があります。

しかし、オブジェクトIDがキー、ポインターが値のハッシュテーブルがあり、参照がオブジェクトアドレスの代わりに上記のIDを指す場合、参照の修正には1つの値の変更のみが必要で、一時停止はオブジェクトの場合にのみ必要ですコピー中に書き込みが試行されます...

私の考え方に間違いはありますか?

回答:


19

一時停止が必要なの、参照の更新だけではありません。「マークスイープ」の下に一般的にグループ化される標準アルゴリズムはすべて、オブジェクトグラフ全体がマークされている間は変更されないままであることを前提としています。修正(新しいオブジェクトの作成、参照の変更)を正しく処理するには、3色アルゴリズムなど、かなりトリッキーな代替アルゴリズムが必要です。包括的な用語は「コンカレントガベージコレクション」です。

ただし、圧縮後の参照の更新にも一時停止が必要です。そして、はい(たとえば、永続オブジェクトIDと実際のポインターへのハッシュテーブルを介して)を使用すると、一時停止を大幅に減らすことができます。望むなら、この部分をロックフリーにすることさえ可能かもしれません。低レベルの共有メモリ同時実行性と同じように正しく動作することは依然として難しいですが、動作しない根本的な理由はありません。

ただし、これには重大な欠点があります。余分なスペース(すべてのオブジェクトに対して少なくとも 2つの余分な単語)を使用することは別として、すべての逆参照をはるかに高価にします。属性を取得するような単純なことでも、ハッシュテーブル全体の検索が必要になります。パフォーマンスヒットは、インクリメンタルトレースよりもはるかに悪いと推定します。


私たちは1つの命令のみ...のは、50 MBのテーブルとハッシュは可能性が言わせて、単純な剰余を持つことができますので、まあ、私たちは、今日多くのメモリを持っている
mrpyo

3
@mrpyoは、ハッシュテーブルのサイズを取得し、モジュロ演算を行い、ハッシュテーブルオフセットから逆参照して実際のオブジェクトポインタを取得し、オブジェクト自体を逆参照します。さらに、おそらくいくつかのレジスタのシャッフル。最終的に4つ以上の手順になります。また、このスキームにはメモリの局所性に関する問題があります。現在、ハッシュテーブルとデータ自体の両方がキャッシュに収まる必要があります。
アモン14

@mrpyoオブジェクトごとに1つのエントリ(オブジェクトID->現在のアドレス)が必要ですよね?とにかかわらず、ハッシュ関数がどのように安いの、あなたがなり衝突し、それらを解決する必要があります。また、アモンが言ったこと。

@amon CPUに50MB以上のキャッシュがあるのは時間の問題です:)
14

1
@Ӎσᶎチップ上に50 MiBのトランジスタを配置し、L1またはL2キャッシュとして動作するのに十分に低いレイテンシーを維持できるようになるまで(L3キャッシュはすでに最大15 MiBのサイズですが、通常はオフチップのAFAIKレイテンシがL1およびL2よりも悪い)、それに応じて大量のメインメモリ(およびそこに入れるデータ)があります。テーブルのサイズを固定することはできません。ヒープとともに拡大する必要があります。

19

コンピュータサイエンスのすべての問題は、別のレベルの間接参照によって解決できます。ただし、間接参照の層が多すぎるという問題を除きます。

アプローチは、ガベージコレクションの問題をすぐには解決せず、1レベル上に移動するだけです。そして、何の費用で!現在、すべてのメモリアクセスは、別のポインター逆参照を通過します。結果の場所をキャッシュすることはできません。その間に場所が変更された可能性があるため、常にオブジェクトIDを確認する必要があります。ほとんどのシステムでは、このインダイレクションは受け入れられません。世界を止めることは、総ランタイムコストが低いと想定されます。

私は、あなたの命題は問題を動かすだけで、解決するものではないと言いました。問題は、オブジェクトIDの再利用に関するものです。オブジェクトIDは現在、ポインターと同等であり、アドレスの数は限られています。プログラムの存続期間中に、INT_MAXを超えるオブジェクトが、たとえば次のようなループで作成されることが考えられます(特に32ビットシステム)

while (true) {
    Object garbage = new Object();
}

各オブジェクトのオブジェクトIDをインクリメントするだけであれば、ある時点でIDが不足します。したがって、どのIDがまだ使用中で、どのIDが解放されているかを調べて、それらを再利用できるようにする必要があります。おなじみの音?今、私たちは正方形に戻っています。


おそらく「十分に大きい」IDを使用できますか?このアイデアは全体的に良いとは言っていませんが、ほぼ確実にIDSの再利用を回避できます。
バリティ14

@Valityは現実的にはいです– IDの再利用の問題を回避できることがわかる限りです。しかし、これは別の「640Kで誰にでも十分であるべき」という議論に過ぎず、実際には問題を解決しません。より壊滅的な側面は、すべてのオブジェクト(およびハッシュテーブル)のサイズをこれらのオーバーサイズの擬似ポインターに対応するために大きくする必要があり、ハッシュアクセス中にこのbigintを他のIDと比較する必要があることです。 、複数の命令を実行して完了します(64ビットの場合:8倍の負荷、4倍の比較、3倍、およびネイティブの整数に対して5倍の増加)。
アモン14

ええ、しばらくするとIDが不足し、一時停止が必要なIDをすべて変更する必要があります。しかし、おそらくそれはまれなイベント...だろう
mrpyo

@amon非常に同意し、すべての非常に良い点は、私が同意する真に持続可能なシステムを持つことははるかに良いです。これは、あなたがそうすることが何であれ、理論的には興味深いだけです。P:個人的に私はとにかくしかし、大きなガベージコレクタのファンではない
Vality

@amon:64ビットID(584年のナノ秒)をラップするとうまくいかないコードが世界中にあります。特にグローバルカウンターをシャードしない場合は、おそらく1nsのメモリ割り当てを手配できます。 IDを吐き出します!)。しかし、確かに、あなたがそれに依存する必要がないなら、あなたはそうしません。
スティーブジェソップ14

12

あなたの考え方に誤りはありません。元のJavaガベージコレクターがどのように機能したかに非常に近いものを説明したところです。

元のJava仮想マシン[6]および一部のSmalltalk仮想マシンは、[6]でハンドルと呼ばれる間接ポインターを使用してオブジェクトを参照します。ハンドルを使用すると、ハンドルを使用すると、各オブジェクトへの直接ポインタが1つだけ存在するため、ガベージコレクション中にオブジェクトを簡単に再配置できます。ハンドルを介した間接的なオブジェクトへのその他すべての参照。このようなハンドルベースのメモリシステムでは、オブジェクトアドレスはオブジェクトの存続期間にわたって変化するため、ハッシュには使用できませんが、ハンドルアドレスは一定のままです。

ガベージコレクションされたオブジェクトのスペースと時間の効率的なハッシュ

SunのJava仮想マシンの現在の実装では、クラスインスタンスへの参照は、それ自体がポインタのペアであるハンドルへのポインタです。オブジェクトのメソッドを含むテーブルへのポインタと、オブジェクトのタイプ、およびオブジェクトデータ用のJavaヒープから割り当てられたメモリへのもう一方。

Java仮想マシン仕様(1997)

そのため、機能し、試行され、その非効率性が世代別マークおよびスイープシステムの開発につながりました。


おそらく、これらのハンドルは(質問のように)ハッシュテーブルのキーではなかったのでしょうか?ポインタを含む構造体だけが必要です。その場合、ハンドルはすべて同じサイズであるため、ヒープアロケータから割り当てることができます。断片化されないため、その性質上、内部圧縮は必要ありません。そのアロケーターによって使用される大きなブロックが、それ自体が再配置できないことを嘆くかもしれません。これは別のレベルの間接参照によって解決できます;-)
スティーブジェソップ14

@SteveJessopはい、gcの実装にはハッシュテーブルがありませんでしたが、ハンドルの値は次によって返される値でもありましたObject.getHashCode()
Pete Kirkham 14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.