最近では、非常に多くの言語がガベージコレクションされています。サードパーティによってC ++でも利用可能です。しかし、C ++にはRAIIとスマートポインターがあります。それでは、ガベージコレクションを使用する意味は何ですか?何か特別なことをしていますか?
また、C#などの他の言語では、すべての参照が仕様と実装によってスマートポインターとして扱われる場合(RAIIは別として)、ガベージコレクターの必要性はまだありますか?いいえの場合、なぜそうではないのですか?
最近では、非常に多くの言語がガベージコレクションされています。サードパーティによってC ++でも利用可能です。しかし、C ++にはRAIIとスマートポインターがあります。それでは、ガベージコレクションを使用する意味は何ですか?何か特別なことをしていますか?
また、C#などの他の言語では、すべての参照が仕様と実装によってスマートポインターとして扱われる場合(RAIIは別として)、ガベージコレクターの必要性はまだありますか?いいえの場合、なぜそうではないのですか?
回答:
それでは、ガベージコレクションを使用する意味は何ですか?
参照カウントのスマートポインターを意味すると仮定していますが、これらは(基本的な)ガベージコレクションの形式であることに注意してください。代わりに。
精度。参照カウントだけではサイクルがリークするため、参照カウントのスマートポインターは、サイクルをキャッチするために他の手法を追加しない限り、一般にメモリをリークします。これらの手法が追加されると、参照カウントの単純さの利点はなくなりました。また、スコープベースの参照カウントとトレースGCは異なる時間に値を収集します。参照カウントはより早く収集され、トレースGCはより早く収集されることもあります。
スループット。スマートポインターは、特に参照カウントがアトミックにバンプされるマルチスレッドアプリケーションのコンテキストでは、ガーベッジコレクションの最も効率の悪い形式の1つです。これを軽減するために設計された高度な参照カウント手法がありますが、実稼働環境ではGCのトレースが依然として最適なアルゴリズムです。
レイテンシー。典型的なスマートポインターの実装では、デストラクタが雪崩を起こし、無制限の一時停止時間が発生します。ベーカーのトレッドミルなど、他の形式のガベージコレクションははるかにインクリメンタルであり、リアルタイムにすることもできます。
誰もこの角度からそれを見ていないので、私はあなたの質問を言い換えます:あなたが図書館でそれをすることができるなら、なぜ言語に何かを入れますか?特定の実装と構文の詳細を無視すると、GC /スマートポインターは基本的にその質問の特別なケースです。ライブラリに実装できるのに、言語自体でガベージコレクタを定義するのはなぜですか?
その質問にはいくつかの答えがあります。最も重要な最初の:
すべてのコードがそれを使用して相互運用できることを確認します。これが、コードの再利用とコード共有がJava / C#/ Python / Rubyまで実際にうまくいかなかった大きな理由だと思います。ライブラリは通信する必要があり、ライブラリが持つ唯一の信頼できる共有言語は、言語仕様自体(およびある程度、その標準ライブラリ)にあるものです。ライブラリをC ++で再利用しようとしたことがあるなら、おそらく標準のメモリセマンティクスによって引き起こされる恐ろしい痛みを経験したことはないでしょう。構造体をlibに渡したい。参照を渡しますか?ポインター?scoped_ptr
?smart_ptr
?所有権を渡しますか?それを示す方法はありますか?ライブラリを割り当てる必要がある場合はどうなりますか?アロケーターを与える必要がありますか?メモリ管理を言語の一部にしないことにより、C ++は各ライブラリペアに独自の特定の戦略をここでネゴシエートするように強制し、それらすべてに同意させるのは本当に困難です。GCはそれを完全な非発行にします。
その周りの構文を設計できます。C ++はメモリ管理自体をカプセル化しないため、ユーザーレベルのコードですべての詳細を表現できるように、一連の構文フックを提供する必要があります。ポインター、参照const
、、間接参照演算子、間接演算子、アドレスなどがあります。メモリ管理を言語自体にロールインする場合、それを中心に構文を設計できます。これらの演算子はすべて消え、言語はより簡潔でシンプルになります。
高い投資収益率が得られます。与えられたコードが生成する値は、それを使用する人の数で乗算されます。これは、ユーザー数が多いほど、ソフトウェアに費やす余裕ができることを意味します。機能を言語に移動すると、その言語のすべてのユーザーがその機能を使用します。これは、これらのユーザーのサブセットのみが使用するライブラリに割り当てるよりも多くの労力を割り当てることができることを意味します。これが、JavaやC#のような言語が絶対に一流のVMと非常に高品質のガベージコレクターを備えている理由です。これらの開発コストは、何百万人ものユーザーで償却されます。
Dispose
ビットマップをカプセル化するオブジェクトを呼び出すと、そのオブジェクトへの参照は破棄されたビットマップオブジェクトへの参照になります。他のコードがまだそれを使用することを期待している間にオブジェクトが時期尚早に削除された場合、ビットマップクラスは他のコードが予測可能な方法で失敗することを保証できます。対照的に、解放されたメモリへの参照を使用することは、未定義の動作です。
ガベージコレクションとは、基本的に、割り当てられたオブジェクトが到達できなくなった時点で自動的に解放されることを意味します。
より正確に言えば、それらはプログラムに到達できなくなったときに解放されます。そうしないと、循環参照されるオブジェクトは決して解放されないからです。
スマートポインターとは、通常のポインターのように動作するが、追加の機能が付加された構造を指します。これらには、割り当て解除だけでなく、コピーオンライト、バインドされたチェックなども含まれます。
さて、あなたが述べたように、スマートポインターを使用して、ガベージコレクションの形式を実装できます。
しかし、思考の流れは次のようになります。
もちろん、最初からこのように設計できます。C#はガベージコレクションを行うように設計されているためnew
、参照がスコープ外になったときにオブジェクトとオブジェクトのみが解放されます。これを行う方法はコンパイラ次第です。
しかし、C ++では、ガベージコレクションは意図されていませんでした。ポインターを割り当ててint* p = new int;
スコープ外になった場合、p
それ自体はスタックから削除されますが、割り当てられたメモリを管理する人はいません。
今、あなたが最初から持っている唯一のものは決定論的なデストラクタです。オブジェクトが作成されたスコープを離れると、そのデストラクタが呼び出されます。テンプレートおよび演算子のオーバーロードと組み合わせて、ポインターのように動作するラッパーオブジェクトを設計できますが、デストラクタ機能を使用して、それに接続されたリソース(RAII)をクリーンアップします。これをスマートポインターと呼びます。
これはすべてC ++固有です。演算子のオーバーロード、テンプレート、デストラクタなどです。この特定の言語の状況では、必要なGCを提供するスマートポインターを開発しました。
ただし、最初からGCを使用して言語を設計する場合、これは実装の詳細にすぎません。オブジェクトがクリーンアップされると言うだけで、コンパイラがこれを行います。
C ++のようなスマートポインターは、決定論的な破壊をまったく持たないC#のような言語ではおそらく不可能です(C#は.Dispose()
、特定のオブジェクトでを呼び出すための構文シュガーを提供することでこれを回避します)。参照されていないリソースは最終的にGCによって回収されますが、正確にこれが発生するときは未定義です。
そしてこれにより、GCがより効率的に作業を行えるようになります。.NET GCは、その上に設定されているスマートポインターよりも言語に深く組み込まれているため、メモリ操作を遅延させてブロック単位で実行することで、より安価にしたり、オブジェクトの頻度に基づいて効率を上げるためにメモリを移動したりできますアクセスされます。
IDisposable
およびを介した決定論的な破壊の形式がありusing
ます。しかし、それはプログラマの努力を少し必要とします。そのため、通常はデータベース接続ハンドルなどの非常に乏しいリソースにのみ使用されます。
IDisposable
だけで、従来を交換して構文をlet ident = value
することによってuse ident = value
...
using
ガベージコレクションとはまったく関係なく、変数がC ++のデストラクタのようにスコープから外れると関数を呼び出します。
私の考えでは、ガベージコレクションとメモリ管理に使用されるスマートポインターには2つの大きな違いがあります。
前者は、GCがスマートポインターでは収集できないガベージを収集することを意味します。スマートポインターを使用している場合は、この種のガベージの作成を避けるか、手動で処理する準備をする必要があります。
後者は、スマートポインターがどれほどスマートであっても、その操作によりプログラムの作業スレッドが遅くなることを意味します。ガベージコレクションは作業を延期し、他のスレッドに移動できます。それにより、全体的により効率的になります(実際、最新のGCのランタイムコストは、スマートポインターの余分なオーバーヘッドがなくても、通常のmalloc / freeシステムよりも低くなります)。アプリケーションスレッドの方法。
ここで、プログラムの構成要素であるスマートポインターを使用して、ガベージコレクションの範囲外にある他のあらゆる興味深いこと(Darioの答えを参照)を実行できることに注意してください。それらを行いたい場合は、スマートポインターが必要になります。
ただし、メモリ管理の目的上、ガベージコレクションに代わるスマートポインターの見込みはありません。彼らは単にそれが得意ではありません。
using
C#の後続バージョンにブロックを導入しました。さらに、リアルタイムシステムではGCの非決定的な動作が禁止される場合があります(そのため、GCはそこで使用されません)。また、GCは非常に複雑であるため、ほとんどの場合実際にメモリリークが発生し、非常に効率が悪い(例:Boehm…)。
ガベージコレクションという用語は、収集するガベージがあることを意味します。C ++では、スマートポインターには複数のフレーバーがあり、最も重要なのはunique_ptrです。unique_ptrは、基本的に単一の所有権とスコープ構造です。適切に設計されたコードでは、ほとんどのヒープに割り当てられたものは通常unique_ptrスマートポインターの背後にあり、これらのリソースの所有権は常に適切に定義されます。unique_ptrにはオーバーヘッドがほとんどなく、unique_ptrは、伝統的に人々を管理された言語に追い込んだ手動メモリ管理の問題のほとんどを取り除きます。より多くのコアが同時に実行されるようになった現在、パフォーマンスを向上させるために、任意の時点で一意で明確に定義された所有権を使用するようにコードを駆動する設計原則がより重要になっています。
適切に設計されたプログラム、特にマルチスレッド環境でも、共有データ構造なしではすべてを表現できるわけではなく、本当に必要なデータ構造では、スレッドが通信する必要があります。c ++のRAIIは、シングルスレッド設定でのライフタイムの問題に対して非常によく機能します。マルチスレッド設定では、オブジェクトのライフタイムが階層的に完全にスタック定義されない場合があります。これらの状況では、shared_ptrの使用がソリューションの大部分を提供します。リソースの共有所有権を作成し、これがC ++で唯一のガベージを見る場所ですが、適切に設計されたc ++プログラムは、完全なガベージコレクションよりも、共有PTRを使用した「リター」コレクションを実装するために考慮する必要があります他の言語で実装されています。C ++には、それほど多くの「ゴミ」はありません
他の人が述べたように、参照カウントのスマートポインターはガベージコレクションの1つの形式であり、そのためには1つの大きな問題があります。参照カウント形式のガベージコレクションの欠点として主に使用される例は、互いに収集されないオブジェクトクラスターを作成するスマートポインターで互いに接続された孤立したデータ構造の作成に関する問題です。計算のアクターモデルに従って設計されたプログラムでは、大部分で主に使用されるように、マルチスレッドプログラミングに幅広い共有データアプローチを使用する場合、データ構造は通常、C ++でそのような収集不可能なクラスターを発生させません業界では、これらの孤立したクラスターがすぐに現実になる可能性があります。
つまり、共有ポインターの使用によって、unique_ptrの幅広い使用と、マルチスレッドプログラミングのための計算手法のアクターモデルとshared_ptrの限定的な使用を意味する場合、他の形式のガベージコレクションは何も買わない追加の利点。しかし、すべてを共有するアプローチでは、場所全体でshared_ptrが必要になる場合は、同時実行モデルの切り替え、または所有権のより広い共有とデータ構造への同時アクセスを対象としたマネージ言語への切り替えを検討する必要があります。
Rust
ガベージコレクションが必要ないということですか?
ほとんどのスマートポインターは、参照カウントを使用して実装されます。つまり、オブジェクトを参照する各スマートポインターは、オブジェクトの参照カウントを増やします。そのカウントがゼロになると、オブジェクトは解放されます。
問題は、循環参照がある場合です。つまり、AにはBへの参照があり、BにはCへの参照があり、CにはAへの参照があります。スマートポインターを使用している場合、A、BおよびCに関連付けられたメモリを解放するには、手動でそこに循環参照を「ブレーク」します(weak_ptr
C ++で使用するなど)。
ガベージコレクション(通常)の動作はまったく異なります。最近のほとんどのガベージコレクターは、到達可能性テストを使用しています。つまり、スタック上の参照とグローバルにアクセス可能なもののすべてを見て、その後、すべてのは、それらの参照が参照するオブジェクトトレースし、あり、そしてオブジェクト彼らは他のすべてがゴミであるなど、を参照してください。
この方法では、循環参照はもはや重要ではありません-A、B、Cのいずれにも到達できない限り、メモリを再利用できます。
「実際の」ガベージコレクションには他にも利点があります。たとえば、メモリ割り当ては非常に安価です。メモリブロックの「終了」へのポインタを増やすだけです。割り当て解除には、一定の償却コストもあります。しかし、もちろんC ++のような言語を使用すると、メモリ管理を自由に実装できます。そのため、より高速な割り当て戦略を思い付くことができます。
もちろん、C ++では、ヒープに割り当てられたメモリの量は通常、C#/。NETなどの参照が多い言語よりも少なくなります。しかし、それは実際にはガベージコレクションとスマートポインターの問題ではありません。
いずれにせよ、この問題は、一方が他方より優れているということではありません。それぞれに長所と短所があります。
パフォーマンスについてです。メモリの割り当てを解除するには、多くの管理が必要です。割り当て解除がバックグラウンドで実行される場合、フォアグラウンドプロセスのパフォーマンスが向上します。残念ながら、メモリ割り当ては遅延することはできません(割り当てられたオブジェクトは次の聖なる瞬間に使用されます)が、オブジェクトを解放することはできます。
C ++(GCなし)で試して、大量のオブジェクトを割り当て、「hello」を出力してから削除します。オブジェクトを解放するのにどれくらい時間がかかるか驚くでしょう。
また、GNU libcはメモリの割り当てを解除するためのより効果的なツールを提供します。obstacksを参照してください。気づかなければならない、私はobstacksの経験がなく、それらを使ったことがない。
ガベージコレクションはより効率的になる可能性があります。基本的には、メモリ管理のオーバーヘッドを「まとめ」、一度にすべて処理します。一般に、これによりメモリの割り当て解除に費やされる全体的なCPUは少なくなりますが、それはある時点で割り当て解除アクティビティの大きなバーストがあることを意味します。GCが適切に設計されていないと、GCがメモリの割り当てを解除しようとしている間、これが「一時停止」としてユーザーに表示される可能性があります。最近のGCのほとんどは、最も悪条件の場合を除いて、ユーザーにこれを見えないようにするのに非常に優れています。
スマートポインター(または任意の参照カウントスキーム)には、コードを見たときに正確に発生するという利点があります(スマートポインターが範囲外になると、事が削除されます)。あちこちで割り当て解除のバーストが少し発生します。全体的に割り当て解除により多くのCPU時間を使用する可能性がありますが、プログラムで発生しているすべての事柄に分散しているため、ユーザーに表示される可能性は低くなります(一部のモンスターデータ構造の割り当て解除がむき出しになります)。
応答性が重要な場所で何かをしている場合、スマートポインター/参照カウントを使用すると、発生していることを正確に知ることができるため、コーディング中にユーザーに表示される可能性が高いものを知ることができます。GCの設定では、ガベージコレクターを制御するのは最も短命であり、単純に回避する必要があります。
一方、全体的なスループットを目標とする場合は、メモリ管理に必要なリソースを最小限に抑えるため、GCベースのシステムの方がはるかに優れた選択肢です。
サイクル:サイクルの問題を重要な問題とは考えていません。スマートポインターを使用するシステムでは、サイクルを持たないデータ構造を使用する傾向があるか、単純にそのようなものを手放すことに注意します。必要に応じて、所有オブジェクト内のサイクルを破壊する方法を知っているキーパーオブジェクトを使用して、適切な破壊を自動的に保証できます。プログラミングの一部の領域では、これは重要な場合がありますが、ほとんどの日常業務では関係ありません。
それはスペクトルです。
パフォーマンスを厳しく制限せず、グラインドを実行する準備ができている場合は、アセンブリまたはcで終了します。 、それを台無しにするすべての自由:
「何をすべきか教えてあげる、あなたはそれをする。私を信じて」。
ガベージコレクションは、スペクトルのもう一方の端です。あなたにはほとんど制御がありませんが、それはあなたのために世話をしました:
「私が欲しいものを教えます、あなたはそれを実現させます」。
これには多くの利点があります。ほとんどの場合、リソースが不要になった時期を正確に知ることに関してはそれほど信頼できる必要はありませんが、(ここに浮かぶ答えのいくつかにもかかわらず)パフォーマンスには向いていません。パフォーマンスの予測可能性。(すべてのことと同様に、コントロールを与えられて愚かなことをすると、悪い結果になる可能性があります。しかし、コンパイル時にメモリを解放できる条件を知ることは、パフォーマンスの向上として使用できないことを示唆するためです。ナイーブを超えて)。
RAII、スコーピング、参照カウントなどはすべて、そのスペクトルに沿ってさらに移動できるようにするためのヘルパーですが、そこまで行くことはできません。これらはすべて、引き続き積極的に使用する必要があります。ガベージコレクションとは異なる方法で、メモリ管理とやり取りするように要求します。
結局、すべてが命令を実行するCPUに要約されることを覚えておいてください。私の知る限り、すべてのコンシューマグレードのCPUには命令セットがあり、メモリ内の特定の場所にデータを保存する必要があり、そのデータへのポインタがあります。それが基本レベルのすべてです。
それに加えて、ガベージコレクション、移動された可能性のあるデータへの参照、ヒープコンパクションなどが、上記の「アドレスポインター付きメモリチャンク」パラダイムによって与えられた制限内で作業を行っています。スマートポインターについても同じことです。実際のハードウェアでコードを実行する必要があります。