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