collection なぜ参照カウント+ガベージコレクションはC#でですか?




jvm garbage collection (8)

私はC + +の背景から来て、私は約1年間C#で作業してきました。 他の多くの人々と同様に、私は決定的なリソース管理がなぜ言語に組み込まれていないのか誤解しています。 確定的なデストラクタの代わりに、disposeパターンがあります。 人々はIDisposable癌をコードで広めることがその努力に値するのかどうか疑問に思う

私のC ++でバイアスされた脳では、決定論的なデストラクタで参照カウントされたスマートポインタを使用するのは、IDisposableを実装し、メモリ以外のリソースをクリーンアップするためにdisposeを呼び出さなければならないガベージコレクタからの大きな一歩です。 確かに、私はあまりスマートではありません...私は純粋に物事が彼らのようになっている理由をよりよく理解したいという願いからこれを求めています。

C#が次のように変更された場合はどうなりますか?

オブジェクトは参照カウントされます。 オブジェクトの参照カウントが0になると、リソースクリーンアップメソッドがオブジェクトに対して確定的に呼び出され、オブジェクトがガベージコレクションの対象としてマークされます。 ガベージコレクションは、将来、メモリが再利用される時点で、非決定的な時間に発生します。 このシナリオでは、IDisposableを実装する必要はなく、Disposeを呼び出す必要があります。 解放するメモリ以外のリソースがある場合は、リソースクリーンアップ機能を実装するだけです。

  • なぜそれは悪い考えですか?
  • それはガベージコレクタの目的を破るだろうか?
  • そのようなことを実現することは実現可能でしょうか?

編集:これまでのコメントから、これは悪い考えです。

  1. GCは参照カウントなしでより高速です
  2. オブジェクトグラフのサイクルを扱う際の問題

1番は有効だと思いますが、2番は弱い参照を使いやすいです。

速度の最適化はあなたの欠点よりも重要です:

  1. タイムリーに非メモリリソースを解放することはできません
  2. あまりにも早く非メモリリソースを解放する可能性があります

リソースクリーンナップの仕組みが決定的で、言語に組み込まれている場合は、その可能性を排除できます。


IDisposableを実装しているオブジェクトは、ユーザーがDisposeを明示的に呼び出していないときにGCによって呼び出されるファイナライザも実装する必要があります( MSDNのIDisposable.Disposeを参照)。

IDisposableの全体的なポイントは、GCがいくつかの非決定的な時間に実行され、貴重なリソースを保持しており、確定的な時間に解放したいのでIDisposableを実装することです。

だからあなたの提案はIDisposableの点で何も変わらないでしょう。

編集:

ごめんなさい。 あなたの提案を正しく読まなかった。 :-(

Wikipediaには、 参考文献数のGCの欠点の簡単な説明があります


Brad Abramsは、.Netフレームワークの開発中に書かれたBrian Harryの電子メールを掲載しまし 。 初期の優先順位の1つが、参照カウントを使用するVB6との意味的同等性を保つことであったとしても、参照カウントが使用されなかった多くの理由が詳しく説明されています。 いくつかのタイプがカウントされ、他のタイプはカウントされない( IRefCounted !)か、特定のインスタンスがカウントされているかどうか、そしてこれらの解決策のどれもが許容可能とみなされない理由が考えられます。

[リソース管理と決定論的ファイナンシングの問題]は非常に敏感なトピックなので、私はできる限り正確かつ完全な説明にしようとしています。 私はメールの長さをお詫び申し上げます。 このメールの最初の90%は、問題が本当に難しいとあなたに納得させようとしています。 その最後の部分では、私たちがしようとしていることについて話しますが、なぜこれらのオプションを見ているのかを理解するためには最初の部分が必要です。

...

最初は、自動リフカウント (プログラマが忘れることができないように)と、サイクルを自動的に検出して処理するためのいくつかの他のものの形を取るという前提から始めました。 ...私たちは最終的にこれが一般的なケースではうまくいかないと結論付けました。

...

要約すれば:

  • プログラマが複雑なデータ構造の問題を理解し、追跡し、設計することなく、サイクルの問題解決することは非常に重要だと感じています。
  • 私たちは高性能(速度とワーキングセットの両方)のシステムを持っていることを確認したいと思っています。分析では、システム内の単一のオブジェクトごとに参照カウントを使用しても、この目標を達成できません
  • コンポジションやキャスティングの問題など、さまざまな理由から、 refカウントが必要なオブジェクトだけを持つ単純な透過的なソリューションはありません
  • 私たちは、他の言語との相互作用を禁止し、言語固有のバージョンを作成することによってクラスライブラリの分岐を引き起こすため、単一の言語/コンテキストに対して確定的なファイナライズを提供するソリューションを選択しないことを選択しました。

私はガベージコレクションについて何か知っています。 完全な説明はこの質問の範囲を超えているので、ここに簡単な要約があります。

.NETは、世代別ガベージコレクタのコピーと圧縮を使用します。 これは参照カウントよりも進んでおり、直接またはチェーンを介して参照するオブジェクトを収集できるという利点があります。

参照カウントはサイクルを収集しません。 参照カウントもスループットが低く(全体的に遅くなりますが)、追跡コレクタよりも早い休止(最大休止時間が短い)の利点があります。


決定的な非メモリリソース管理は言語の一部ですが、デストラクタでは行われません。

あなたの意見は、C ++のバックグラウンドから来てRAIIデザインパターンを使用しようとしている人に共通しています。 C ++では、例外がスローされても、コードの一部がスコープの最後で実行されることを保証する唯一の方法は、オブジェクトをスタックに割り当てて、クリーンアップコードをデストラクタに置くことです。

他の言語(C#、Java、Python、Ruby、Erlangなど)では、代わりにtry-finally(またはtry-catch-finally)を使用して、クリーンアップコードが常に実行されるようにすることができます。

// Initialize some resource.
try {
    // Use the resource.
}
finally {
    // Clean-up.
    // This code will always run, whether there was an exception or not.
}

IC#では、 using構文を使用することもできます。

using (Foo foo = new Foo()) {
    // Do something with foo.
}
// foo.Dispose() will be called afterwards, even if there
// was an exception.

したがって、C ++プログラマにとっては、 "クリーンアップコードの実行"と "メモリの解放"を2つの別個のものとして考えるのに役立つかもしれません。 最終的なブロックにあなたのクリーンアップコードを入れて、メモリの世話をするGCに任せてください。


ここには多くの問題があります。 まず、管理対象メモリの解放と他のリソースのクリーンアップを区別する必要があります。 前者は非常に速く、後者は遅い可能性があります。 .NETでは、2つが分離されているため、管理対象メモリのクリーンアップがより迅速に行えます。 これは、管理対象メモリ以外のものがクリーンアップされている場合にのみ、Dispose / Finalizerを実装する必要があることも意味します。

.NETはマークとスイープの手法を採用しており、オブジェクトにルーツを探してヒープを走査します。 根付いたインスタンスは、ガベージコレクションで生き残ります。 他のすべてはメモリを再生するだけできれいにすることができます。 GCはメモリをコンパクトにしなければならず、複数のインスタンスを再利用しても単純なポインタ操作であるため、メモリを再利用することは別です。 これをC ++の複数のデストラクタ呼び出しと比較してください。


私はC + +の背景から来て、私は約1年間C#で作業してきました。 他の多くの人々と同じように、決定的なリソース管理がなぜ言語に組み込まれていないのか不安です。

using構造体は「決定論的」なリソース管理を提供し、C#言語に組み込まれています。 「決定論的」とは、 usingブロックが実行を開始した後、コードの前にDisposeが呼び出されたことを意味します。 これは「決定論的」という言葉が意味するものではなく、誰もがこのような文脈でそれを悪用するように見えることに注意してください。

私のC ++でバイアスされた脳では、決定論的なデストラクタで参照カウントされたスマートポインタを使用するのは、IDisposableを実装し、メモリ以外のリソースをクリーンアップするためにdisposeを呼び出さなければならないガベージコレクタからの大きな一歩です。

ガベージコレクタではIDisposableを実装する必要はありません。 実際、GCは完全にそれを知らない。

確かに、私はあまりスマートではありません...私は純粋に物事が彼らのようになっている理由をよりよく理解したいという願いからこれを求めています。

トレースガベージコレクションは、無限のメモリマシンをエミュレートするための高速で信頼性の高い方法であり、プログラマが手動メモリ管理の負担から解放されます。 これにより、いくつかのクラスのバグが排除されました(手短に解放され、二重に解放され、解放されるのを忘れていました)。

C#が次のように変更された場合はどうなりますか?

オブジェクトは参照カウントされます。 オブジェクトの参照カウントがゼロになると、リソースクリーンアップメソッドがオブジェクトに対して確定的に呼び出され、

2つのスレッド間で共有されるオブジェクトを考えてみましょう。 スレッドは、参照カウントを0に減らすために競合します。 1つのスレッドがレースに勝ち、もう1つのスレッドがクリーンアップを担当します。 それは非決定論的です。 参照カウントが本質的に決定論的であるという信念は神話です。

もう一つの一般的な神話は、参照カウントがプログラムのできるだけ早い時点でオブジェクトを解放するということです。 それはしません。 デクリメントは常に延期され、通常はスコープの終わりまで延期されます。 これは、必要以上に長く物体を生かし続けることで、「浮遊ごみ」と呼ばれるものを残します。 特に、ガベージコレクタの中には、スコープベースの参照カウント実装よりも早くオブジェクトをリサイクルできるものがあります。

オブジェクトはガベージコレクションのマークが付けられます。 ガベージコレクションは、将来、メモリが再利用される時点で、非決定的な時間に発生します。 このシナリオでは、IDisposableを実装する必要はなく、Disposeを呼び出す必要があります。

とにかくガベージコレクションされたオブジェクトのIDisposableを実装する必要はありません。そのため、メリットはありません。

解放するメモリ以外のリソースがある場合は、リソースクリーンアップ機能を実装するだけです。

なぜそれは悪い考えですか?

純粋な参照カウントは非常に遅く、サイクルをリークします。 たとえば、 BoostのC ++でのshared_ptrは、OCamlのGCのトレースよりも最大10倍遅いです 。 素朴なスコープベースの参照カウントでさえも、マルチスレッドプログラム(ほぼすべての最新プログラム)の存在下では非決定論的です。

それはガベージコレクタの目的を破るだろうか?

まったくではありません。 実際、それは1960年代に発明され、その後54年間にわたり徹底的な学術研究の対象となった悪い考えであり、一般的なケースで参照カウントが失敗すると結論づけています。

そのようなことを実現することは実現可能でしょうか?

絶対に。 初期のプロトタイプ.NETとJVMは参照カウントを使用しました。 彼らはまたそれを吸って、GCを追跡することに有利にそれをドロップしました。

編集:これまでのコメントから、これは悪い考えです。

GCは参照カウントなしでより高速です

はい。 カウンタのインクリメントとデクリメントを延期することで、参照カウントを非常に高速にすることができますが、それは非常に欲しがる決定論を犠牲にし、今日のヒープサイズのGCをトレースするよりも遅いです。 しかし、参照カウントは漸近的に速くなるので、ヒープが本当に大きくなる将来のある時点で、私たちは生産自動化メモリ管理ソリューションでRCを使用し始めるかもしれません。

オブジェクトグラフのサイクルを扱う際の問題

トライアルの削除は、参照カウントされたシステムのサイクルを検出し収集するために特別に設計されたアルゴリズムです。 しかし、それは遅く、非決定論的です。

1番は有効だと思いますが、2番は弱い参照を使いやすいです。

弱い参照を「簡単」と呼ぶことは、現実に勝る希望の勝利です。 彼らは悪夢です。 予測不可能で設計が難しいだけでなく、APIを汚染します。

速度の最適化はあなたの欠点よりも重要です:

タイムリーに非メモリリソースを解放することはできません

適時に無料の非メモリリソースusingしませんか?

あまりにも早く非メモリリソースを解放する可能性があります。リソースクリーンアップメカニズムが決定的であり、言語に組み込まれている場合、それらの可能性を排除できます。

using構文は決定論的であり、言語に組み込まれています。

あなたが本当に尋ねたい質問は、 IDisposableが参照カウントを使用しない理由です。 私の応答は逸話です。私はガベージコレクション言語を18年間使用していましたが、リファレンスカウントに頼る必要はありませんでした。 したがって、私は弱い参照のような付随的な複雑さで汚染されていないより単純なAPIを好む。


ガベージコレクタでは、定義したすべてのクラス/タイプに対してDisposeメソッドを記述する必要はありません。 クリーンアップするために何かを明示的に行う必要があるときにのみ定義します。 ネイティブリソースを明示的に割り当てた場合 たいていの場合、GCは単にオブジェクトの上にnew()のようなことをしてもメモリを再利用します。

GCは参照カウントを行いますが、コレクションを実行するたびにどのオブジェクトが「到達可能」( Ref Count > 0 )であるかを見つけることによって、異なる方法で行います。 。 到達不能オブジェクトが収集されます( Ref Count = 0 )。 このように、ランタイムは、オブジェクトが割り当てられたり解放されるたびにハウスキーピング/更新テーブルを行う必要はありません。

C ++(決定論的)とC#(非決定論的)の唯一の大きな違いは、オブジェクトがクリーンアップされるときです。 オブジェクトがC#で収集される正確な瞬間を予測することはできません。

Umpteenthプラグ:GCの仕組みに本当に関心がある場合に備えて、C#経由CLRのGCに関するJeffrey Richterのスタンドアップの章を読むことをお勧めします。


参照カウント

参照カウントを使用するコストは2倍です。まず、すべてのオブジェクトに特別な参照カウントフィールドが必要です。 通常、これは、各オブジェクトに余分な記憶領域を割り当てる必要があることを意味します。 第2に、1つの参照が別の参照に割り当てられるたびに、参照カウントを調整する必要があります。 これにより、代入文の処理時間が大幅に増加します。

.NETのガベージコレクション

C#はオブジェクトの参照カウントを使用しません。 代わりに、スタックからのオブジェクト参照のグラフを維持し、参照されたすべてのオブジェクトを隠すためにルートからナビゲートします。 グラフ内の参照されたすべてのオブジェクトは、ヒープ内で圧縮され、連続したメモリが将来のオブジェクトに使用できるようになります。 ファイナライズする必要のない参照されていないすべてのオブジェクトのメモリが再利用されます。 参照されていないが、それらで実行されるファイナライザがあるものは、ガベージコレクタがファイナライザをバックグラウンドで呼び出すf-reachableキューと呼ばれる別のキューに移動されます。

上記のGCに加えて、より効率的なガベージコレクションのための世代という概念が使用されています。 これは、以下の概念に基づいています。1.マネージヒープの一部のメモリをマネージヒープ全体よりもコンパクトにする方が高速です。新しいオブジェクトは寿命が短く、古いオブジェクトの寿命は長くなります。3.新しいオブジェクトは互いに関連し、同じ時間にアプリケーションによってアクセスされる

マネージヒープは、0,1、および2の3つの世代に分割されます。新しいオブジェクトはgen 0に格納されます.GCのサイクルで再利用されないオブジェクトは、次の世代に昇格されます。 したがって、ジェネレーション0の新しいオブジェクトがGCサイクル1で存続する場合、ジェネレーション1に昇格されます.GCサイクル2で存続するこれらのオブジェクトはジェネレーション2に昇格されます。ガベージコレクタは3世代しかサポートしないため、将来のコレクションで到達不能と判断されるまで、コレクションは2世代のまま残されます。

ガベージコレクタは、世代0が満杯になり、新しいオブジェクトのメモリを割り当てる必要があるときにコレクションを実行します。 世代0のコレクションが十分なメモリを再利用しない場合、ガベージコレクタは世代1のコレクション、次に世代0のコレクションを実行できます。これで十分なメモリが再利用されない場合、ガベージコレクタは世代2,1、および0のコレクションを実行できます。

したがって、GCは参照カウントよりも効率的です。





reference-counting