スコープベースのメモリ管理(SBMM)または[〜#〜] raii [〜#〜]は、C++コミュニティから一般的に(混乱を招くように)参照されているため、本当に気に入っています。私の知る限り、C++(およびC)を除いて、今日使用されている他の主流言語はSBMM/RAIIをメインのメモリ管理メカニズムにせず、ガベージコレクション(GC)を使用することを好みます。
私はこれがかなり混乱していることに気づきました。
std::shared_ptr
(C++の場合)。なぜSBMMはもっと広く使われていないのですか?その欠点は何ですか?
最初に、メモリが他のすべてのリソースを組み合わせたものよりもはるかに(数十、数百、または数千の時間)一般的であると仮定してみましょう。すべての単一の変数、オブジェクト、オブジェクトメンバーには、メモリを割り当てて、後で解放する必要があります。開くファイルごとに、数十から数百万のオブジェクトを作成して、ファイルから取得したデータを保存します。すべてのTCPストリームは、ストリームに書き込まれるために作成された無制限の数の一時バイト文字列と一緒に送信されます。ここでは同じページにいますか?すばらしいです。
RAIIが機能するためには(Sunの下ですべてのユースケースに対応できる既製のスマートポインターがある場合でも)、ownershipを取得する必要があります。このオブジェクトまたはそのオブジェクトを誰が所有するべきか、誰が所有すべきでないか、および所有権をAからBに移すべきときを分析する必要があります。もちろん、共有所有権をeverythingに使用できますが、そうするとスマートポインター経由でGCをエミュレートします。その時点で、GCを言語に組み込む方がはるかに簡単になりますand速くなります。
ガベージコレクションは、最も一般的に使用されているリソースであるメモリの問題から解放されます。もちろん、他のリソースについても同じ決定を行う必要がありますが、それらはあまり一般的ではなく(上記を参照)、複雑な(共有など)所有権もあまり一般的ではありません。精神的な負担が大幅に軽減されます。
ここで、ガベージコレクションされるall値を作成することの欠点をいくつか挙げます。ただし、両方のメモリセーフGC and値型とRAIIを1つの言語に統合することは非常に難しいため、他の方法でこれらのトレードオフを軽減する方が良いでしょうか?
確定性の喪失は、確定的オブジェクトの有効期間にのみ影響するため、実際にはそれほど悪くはありません。次の段落で説明するように、これらの言語では、ほとんどのリソース(豊富であり、かなり遅延してリサイクルできるメモリを除く)はnotにバインドされています。他にもいくつかの使用例がありますが、私の経験ではまれです。
2番目のポイントである手動リソース管理は、現在、スコープベースのクリーンアップを実行するステートメントを介して対処されますが、このクリーンアップはオブジェクトのライフタイムに結び付けられません(したがって、GCおよびメモリの安全性と相互作用しません)。これは、C#ではusing
、Pythonではwith
、最近のJavaバージョンではtry
- with-resourcesです。
RAIIは、自動参照カウントメモリ管理にも従います。 Perlによって使用されます。参照カウントは実装が簡単で、確定的で、非常に高いパフォーマンスを発揮しますが、循環参照(リークを引き起こす)を処理できないため、一般的に使用されていません。
ガベージコレクションされた言語できない RAIIを直接使用しますが、多くの場合、同等の効果を持つ構文を提供します。 Javaでは、try-with-ressourceステートメントがあります。
_try (BufferedReader br = new BufferedReader(new FileReader(path))) { ... }
_
これは、ブロックの終了時にリソースの.close()
を自動的に呼び出します。 C#にはIDisposable
インターフェイスがあり、.Dispose()
ステートメントを終了するときにusing (...) { ... }
を呼び出すことができます。 Pythonにはwith
ステートメントがあります:
_with open(filename) as f:
...
_
同様に機能します。これに関する興味深いスピンでは、Rubyのファイルオープンメソッドがコールバックを取得します。コールバックが実行された後、ファイルは閉じられます。
_File.open(name, mode) do |f|
...
end
_
Node.jsも同じ戦略を使用していると思います。
私の意見では、ガベージコレクションの最も説得力のある利点は、composabilityが可能なことです。メモリ管理の正確性は、ガベージコレクション環境のローカルプロパティです。各部分を個別に見て、メモリリークの可能性があるかどうかを判断できます。任意の数のメモリ修正部品を組み合わせても、それらは正しいままです。
参照カウントに依存すると、そのプロパティが失われます。アプリケーションがメモリをリークできるかどうかは、参照カウントを使用してアプリケーション全体のグローバルプロパティになります。パーツ間のすべての新しい相互作用は、間違った所有権を使用し、メモリ管理を破壊する可能性があります。
これは、異なる言語のプログラムの設計に非常に目に見える影響を及ぼします。 GC言語のプログラムは、多くの相互作用を持つオブジェクトのスープになりがちですが、GCのない言語では、厳密に制御された制限された相互作用のある構造化されたパーツを好む傾向があります。
クロージャは、ほとんどすべての現代の言語に不可欠な機能です。それらは、GCで実装するのが非常に簡単であり、RAIIを正しく実装するのが非常に困難です(不可能ではありませんが)。その主な機能の1つは、変数の存続期間にわたって抽象化できることです。
C++は、他の誰もがやってから40年しか経っていませんでした。多くの賢い人たちがそれらを正しくするためには、多大な努力が必要でした。対照的に、プログラミング言語の設計と実装の知識がまったくない人が設計して実装した多くのスクリプト言語は、それらを持っています。
- SBMMはプログラムをより確定的にします(オブジェクトが破棄されたときを正確に知ることができます)。
ほとんどのプログラマーにとって、OSは非決定論的であり、そのメモリアロケーターは非決定論的であり、彼らが作成するプログラムのほとんどは並行処理であるため、本質的に非決定論的です。デストラクタがスコープの終わりの直前または直後ではなく、正確に呼び出されるという制約を追加することは、大多数のプログラマにとって重要な実用的利点ではありません。
- gCを使用する言語では、多くの場合、手動でリソース管理を行う必要があります(たとえば、Javaでファイルを閉じるを参照)。これは、GCの目的を部分的に損ない、エラーが発生しやすくなります。
C#のusing
およびF#のuse
を参照してください。
- ヒープメモリは、(非常にエレガントに、imo)スコープバインドすることもできます(C++のstd :: shared_ptrを参照)。
つまり、汎用ソリューションであるヒープを取得して、深刻な制限がある特定のケースでのみ機能するように変更することができます。もちろんそうですが、役に立たないのです。
なぜSBMMはもっと広く使われていないのですか?その欠点は何ですか?
SBMMは、実行できることを制限します。
SBMMは 上向きのfunarg問題 をファーストクラスの字句閉包で作成します。これが、閉包がC#のような言語で一般的で使いやすいが、C++ではまれで扱いにくい理由です。プログラミングでは関数構成体を使用する傾向があることに注意してください。
SBMMにはデストラクタが必要です。デストラクタは、関数が戻る前に実行する作業を追加することにより、末尾呼び出しを妨げます。末尾呼び出しは、拡張可能なステートマシンに役立ち、.NETなどによって提供されます。
一部のデータ構造とアルゴリズムは、SBMMを使用して実装することが難しいことで有名です。基本的に、サイクルが自然に発生する場所ならどこでも。最も注目すべきは、アルゴリズムのグラフです。事実上、独自のGCを作成することになります。
ここでは、制御フロー、したがってオブジェクトの存続期間は本質的に非決定的であるため、並行プログラミングはより困難です。メッセージパッシングシステムの実用的な解決策は、メッセージの深いコピーと過度に長いライフタイムの使用です。
SBMMは、ソースコードのスコープが終了するまでオブジェクトを存続させます。これは、多くの場合、必要以上に長く、必要以上に長くなる可能性があります。これにより、浮遊ゴミ(リサイクルを待機している到達できないオブジェクト)の量が増加します。対照的に、ガベージコレクションをトレースすると、オブジェクトへの最後の参照が消えた直後にオブジェクトが解放される傾向があります。 メモリ管理の神話:プロンプト を参照してください。
SBMMは非常に制限されているため、ライフタイムをネストさせることができない状況では、プログラマーはエスケープルートを必要とします。 C++では、shared_ptr
はエスケープルートを提供しますが、 ガベージコレクションのトレースよりも約10倍遅くなる可能性があります 。したがって、GCの代わりにSBMMを使用すると、ほとんどの場合、ほとんどの人が誤った足を踏み入れることになります。しかし、それが役に立たないと言っているのではありません。 SBMMは、リソースが限られているシステムや組み込みプログラミングのコンテキストで依然として価値があります。
FWIWあなたは、ForthとAdaをチェックして、Nicolas Wirthの仕事について読むことができます。
TIOBE(もちろん議論の余地はありますが、これを使用しても問題ないでしょう)のような人気の指標を見ると、上位20の〜50%が「スクリプト言語」または「SQL方言」であることが最初にわかります「、「使いやすさ」と抽象化の手段は、決定論的な動作よりもはるかに重要です。残りの「コンパイル済み」言語から、SBMMを使用する言語の約50%と、使用しない言語の約50%があります。したがって、スクリプト言語を計算から除外する場合、仮定は間違っていると思います。コンパイルされた言語の中で、SBMMを使用するものは使用しないものと同じくらい人気があります。
まだ誰も言及していないGCシステムの1つの主な利点は、GCシステム内の参照は、存在する限りそのアイデンティティを保持することが保証されますです。参照のコピーが存在するときにオブジェクトでIDisposable.Dispose
(.NET)またはAutoCloseable.Close
(Java)を呼び出すと、それらのコピーは引き続き同じオブジェクトを参照します。オブジェクトはもはや何の役にも立ちませんが、それを使用しようとすると、オブジェクト自体によって制御される予測可能な動作になります。対照的に、C++では、コードがオブジェクトに対してdelete
を呼び出し、後でそれを使用しようとすると、システム全体の状態が完全に未定義になります。
注意すべきもう1つの重要な点は、スコープベースのメモリ管理は、明確に定義された所有権を持つオブジェクトに対して非常にうまく機能することです。所有権が定義されていないオブジェクトでは、うまく機能しません。一般に、可変オブジェクトには所有者が必要ですが、不変オブジェクトには必要はありませんが、しわがあります。コードが可変タイプのインスタンスを使用して不変データを保持することは、参照が公開されないようにすることで非常に一般的ですインスタンスを変更する可能性のあるコード。このようなシナリオでは、可変クラスのインスタンスが複数の不変オブジェクト間で共有される可能性があるため、明確な所有権がありません。