ガベージコレクターは、収集ごとにメモリ全体がスキャンされるのをどのように防止しますか?


16

一部の(少なくともMonoおよび.NETの)ガベージコレクタには、頻繁にスキャンする短期メモリ領域と、あまり頻繁にスキャンしないセカンダリメモリ領域があります。Monoはこれをナーサリと呼びます。

どのオブジェクトを破棄できるかを調べるために、ルート、スタック、レジスタから始まるすべてのオブジェクトをスキャンし、参照されなくなったすべてのオブジェクトを破棄します。

私の質問は、使用中のすべてのメモリが収集ごとにスキャンされるのをどのように防ぐのですか?原則として、使用されなくなったオブジェクトを見つける唯一の方法は、すべてのオブジェクトとそのすべての参照をスキャンすることです。ただし、これにより、アプリケーションで使用されていなくても、OSがメモリをスワップアウトできなくなり、「Nursery Collection」でも実行する必要がある膨大な作業のように感じます。保育園を利用することで、彼らが多くを獲得しているとは思えません。

私は何かを見逃していますか、またはガベージコレクタは実際にすべてのオブジェクトとすべての参照をスキャンするたびにスキャンしますか?


1
素晴らしい概要は、アンジェリカランガーによって書かれた「ガベージコレクションチューニングの芸術」という記事にあります。正式には、それがJavaでどのように行われるかについてですが、提示された概念はほとんど言語にとらわれませ
ん-gnat

回答:


14

世代別ガベージコレクションを使用して、すべての古い世代のオブジェクトをスキャンする必要がないようにする基本的な観察は次のとおりです。

  1. コレクションの後、まだ存在するすべてのオブジェクトは最小世代になります(たとえば、.netでは、Gen0コレクションの後、すべてのオブジェクトはGen1またはGen2、Gen1またはGen2コレクションの後、すべてのオブジェクトはGen2)。
  2. オブジェクトまたはその一部は、すべてを世代N以上に昇格させたコレクション以降に作成されていないため、下位世代のオブジェクトへの参照を含めることはできません。
  3. オブジェクトが特定の世代に達した場合、より低い世代を収集するときにその保持を確保するために到達可能として識別される必要はありません。

多くのGCフレームワークでは、ガベージコレクターがオブジェクトまたはその一部にフラグを立てて、オブジェクトへの最初の書き込みが、変更されたという事実を記録する特別なコードをトリガーするようにできます。生成に関係なく、変更されたオブジェクトまたはその一部は、新しいオブジェクトへの参照が含まれている可能性があるため、次のコレクションでスキャンする必要があります。一方、コレクション間で変更されない古いオブジェクトが多数存在することは非常に一般的です。低世代のスキャンではこのようなオブジェクトを無視できるため、そうでない場合よりもはるかに迅速にスキャンを完了できます。

ただし、オブジェクトの変更を検出できず、各GCパスですべてをスキャンする必要がある場合でも、世代別ガベージコレクションにより、圧縮コレクタの「スイープ」ステージのパフォーマンスが向上する可能性があります。一部の組み込み環境(特に、シーケンシャルメモリアクセスとランダムメモリアクセスの速度にほとんどまたはまったく違いがない環境)では、メモリブロックの移動は参照のタグ付けに比べて比較的高価です。そのため、世代別コレクタを使用して「マーク」フェーズを高速化できない場合でも、「スイープ」フェーズを高速化する価値があります。


メモリブロックの移動はどのシステムでも高価であるため、クアッドGhz CPUシステムでもスイープを改善することは効果的です。
gbjbaanb

@gbjbaanb:多くの場合、オブジェクトの移動が完全に無料であっても、ライブオブジェクトを見つけるためにすべてをスキャンするコストは多大であり、好ましくありません。したがって、実際には古いオブジェクトをスキャンしないようにする必要があります。一方、古いオブジェクトの圧縮を控えることは、単純な最適化であり、単純なフレームワークでも実現できます。ところで、小規模な組み込みシステム用のGCフレームワークを設計している場合、不変オブジェクトの宣言的なサポートが役立つ可能性があります。可変オブジェクトが変更されたかどうかを追跡するのは難しいですが、うまくいくかもしれません
...-supercat

...単に、GCパスごとに可変オブジェクトをスキャンする必要があるが、不変オブジェクトはスキャンしないと仮定します。不変オブジェクトを構築する唯一の方法が可変スペースで「プロトタイプ」を構築してからコピーすることであったとしても、1回の余分なコピー操作により、将来のGC操作でオブジェクトをスキャンする必要がなくなります。
-supercat

ちなみに、1980年代の6502マイクロプロセッサ(およびおそらくその他)のBASICのMicrosoft派生実装でのガベージコレクションのパフォーマンスは、場合によっては大幅に向上する可能性があります。 「文字列スペース」ポインタへの文字列割り当て」ポインタ。このような変更により、ガベージコレクターは古い文字列を調べて、それらがまだ必要かどうかを確認できなくなります。Commodore 64はほとんどハイテクではありませんでしたが、そのような「世代」GCはそこでも役立ちます。
-supercat

7

参照しているGCは世代別ガベージコレクターです。これらは、「乳児死亡率」または「世代仮説」として知られる観察結果を最大限に活用するように設計されています。つまり、ほとんどのオブジェクトは非常に迅速に到達できなくなります。彼らは確かにルートからスキャンを開始しますが、すべての古いオブジェクトを無視します。したがって、メモリ内のほとんどのオブジェクトをスキャンする必要はなく、若いオブジェクトのみをスキャンします(少なくともその時点では到達不能な古いオブジェクトを検出しないという犠牲を払います)。

「しかし、それは間違っています」と、「古いオブジェクトは若いオブジェクトを参照することができ、実際に参照する」と叫ぶのが聞こえます。あなたは正しい、そしてそれに対するいくつかの解決策があり、それらはすべて知識の獲得、迅速かつ効率的な取得を中心に展開し、どの古いオブジェクトをチェックする必要があり、無視しても安全です。それらは、オブジェクトの記録、または若い世代へのポインタを含むメモリの小さな範囲(オブジェクトよりも大きいが、ヒープ全体よりもはるかに小さい)にかなり要約されています。他の人は私よりもはるかに優れたものを説明しているので、カードマーキング、記憶セット、書き込み障壁などのキーワードをいくつか挙げます。他の手法(ハイブリッドを含む)もありますが、これらは私が知っている一般的なアプローチを網羅しています。


3

どのナーサリオブジェクトがまだ生きているかを調べるために、コレクタは最後のコレクション以降に変更されたルートセットと古いオブジェクトのみをスキャンする必要があります。最近変更されていない古いオブジェクトは若いオブジェクトを指すことができないためです。 。そこ(突然変異が発生した可能性のあるページのセットに変異したフィールドの正確なセットからの)精度のレベルを変更することで、この情報を維持するための異なるアルゴリズムがありますが、それらはすべて、一般的に何らかの関与する書き込みバリアを:コードは、そのすべての参照の実行GCの簿記を更新する、型指定されたフィールドの突然変異。


1

ガベージコレクターの最も古くて単純な世代は、実際にはすべてのメモリをスキャンし、実行中に他のすべての処理を停止する必要がありました。後のアルゴリズムでは、これをさまざまな方法で改善しました-コピー/スキャンをインクリメンタルにする、または並行して実行します。最新のガベージコレクターは、オブジェクトを世代に分離し、世代を超えたポインターを慎重に管理して、古い世代を乱すことなく新しい世代を収集できるようにします。

重要な点は、ガベージコレクターがコンパイラーおよびランタイムの他の部分と密接に連携して、すべてのメモリを監視しているという錯覚を維持することです。


1970年代後半以前のミニコンピュータとメインフレームでどのようなガベージコレクションアプローチが使用されたかはわかりませんが、少なくとも6502マシンのMicrosoft BASICガベージコレクタは、「次の文字列」ポインタをメモリの先頭に設定してから検索します「次の文字列ポインタ」の下にある最上位アドレスを見つけるためのすべての文字列参照。その文字列は「次の文字列ポインタ」のすぐ下にコピーされ、そのポインタはそのすぐ下に置かれます。その後、アルゴリズムが繰り返されます。コードが提供するポインターを
結合

...世代別コレクションのようなもの。各世代のトップのアドレスを維持し、各GCサイクルの前後にいくつかのポインタースワップ操作を追加するだけで、「世代別」コレクションを実装するためにBASICにパッチを当てるのはどれだけ難しいかと思いました。GCのパフォーマンスは依然としてかなり低下しますが、多くの場合、数十秒から10分の1秒に削られる可能性があります。
-supercat

-2

基本的に... GCは「バケット」を使用して、使用中のものとそうでないものを区別します。一度チェックすると、使用されていないものを一掃し、他のすべてを第2世代(第1世代よりも頻繁にチェックされない)に移動し、第2世代でまだ使用されているものを第3世代に移動します。

そのため、第3世代のオブジェクトは通常、何らかの理由で開いたままになっているオブジェクトであり、GCはそこを頻繁にチェックしません。


1
しかし、どのオブジェクトが使用されているかをどのようにして知るのでしょうか?
ピーターファンジンケル

到達可能なコードから到達可能なオブジェクトを追跡します。実行可能なコード(返されたメソッドのコードなど)からオブジェクトに到達できなくなると、GCは収集しても安全であることが
わかります-JohnL

二人とも、GCの効率性についてではなく、GCの正確性について説明しています。質問から判断すると、OPはそれを十分に知っています。

@delnan yesはい。どのオブジェクトが使用されているかをどのように知るのかという質問に答えていました。これがPieterのコメントにありました。
-JohnL

-5

このGCで通常使用されるアルゴリズムは、単純なマークアンドスイープです。

また、これはC#自体ではなく、いわゆるCLRによって管理されるという事実にも注意する必要があります。


それは、Monoのガベージコレクターについて読んで得た気持ちです。しかし、私が理解していないのは、彼らが今までのコレクションの完全なワーキングセットをスキャンしている場合、GEN-0コレクションが非常に高速である世代別コレクターを持っている理由です。たとえば、2GBのワーキングセットでこれを高速化するにはどうすればよいでしょうか。
ピーターファンギンケル

よく、モノのための本当のGCはSGENで、あなたはこの読みくださいmono-project.com/Generational_GCまたはいくつかのオンライン記事schani.wordpress.com/tag/mono infoq.com/news/2011/01/SGenをポイントであるということ、 CLRやCLIのようなこの新しい技術は本当にモジュール化された設計になっており、この言語はバイナリコードを生成する方法ではなく、CLRのために何かを表現する方法に過ぎません。あなたの質問は、アルゴリズムではなく実装の詳細に関するものです。アルゴリズムにはまだ実装がないため、Monoの技術論文や記事を読んでください。
-user827992

よくわかりません。ガベージコレクターが使用する戦略はアルゴリズムではありませんか?
ピーターファンジンケル

2
-1 OPの混乱を防ぎます。GCがCLRの一部であり、言語固有ではないことはまったく関係ありません。A GCは、主に、それがヒープをレイアウトし、到達可能性を決定する方法によって特徴付けられ、後者は、すべてそのために使用されるアルゴリズム(複数可)に関する。アルゴリズムの実装は多数存在する可能性があり、実装の詳細に追いつくべきではありませんが、スキャンされるオブジェクトの数はアルゴリズムだけで決まります。世代別GCは、「世代別仮説」(ほとんどのオブジェクトは若くして死ぬ)を利用しようとする、単純なアルゴリズム+ヒープレイアウトです。これらは素朴ではありません。

4
アルゴリズム!=実装は確かですが、異なるアルゴリズムの実装になる前に、実装はこれをはるかに逸脱することができます。GCの世界では、アルゴリズムの説明は非常に具体的であり、ナーサリコレクションのヒープ全体をスキャンしない、世代間のポインタがどのように検出および格納されるかなどが含まれます。アルゴリズムでは、アルゴリズムの特定のステップにかかる時間はわかりませんが、この質問にはまったく関係ありません。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.