web-dev-qa-db-ja.com

ガベージコレクションは参照カウントとどのように違いますか?

Appleの新しい言語であるSwiftによるiOS開発に関するオンラインコースに取り組み始めました。講師はこの疑問を私の頭に浮かび上がらせました。彼は次の影響について何かを述べました:

参照カウントによってすべてが処理されるため、メモリ管理について心配する必要はありません。これは、ガベージコレクションがどのように機能するかを理解することについて心配する必要がないことを意味します。

これを聞いたとき、「単純に参照カウントを使用できるのに、なぜガベージコレクションを使用するのか」と思いました。

それでは、2つのアプローチをどのように比較しますか?

46
Aaron Anodide

2つのアプローチがどのように比較されるかを理解するには、まず、それらがどのように機能するか、およびそれぞれの弱点を調べる必要があります。

自動参照カウントまたはARCは、オブジェクトへの参照がなくなるとオブジェクトが割り当て解除されるガベージコレクションの形式です。つまり、他の変数がオブジェクトを特に参照しなくなります。 ARCの下の各オブジェクトには、メモリに追加フィールドとして格納されている参照カウンタが含まれています。これは、変数をそのオブジェクトに設定するたびに増分され(つまり、オブジェクトへの新しい参照が作成されます)、設定するたびに減分されます。 nil/nullへのオブジェクトへの参照、または参照がスコープから外れる(つまり、スタックが巻き戻されるときに削除される)場合、参照カウンターがゼロに下がると、オブジェクトは自身を削除し、デストラクタを呼び出して解放します割り当てられたメモリ。以下に示すように、このアプローチには重大な弱点があります。

「メモリ管理について心配する必要はありません。参照カウントによってすべて処理されます。」これは実際には誤解です。ARCが正しく機能するためには、特定の条件、つまり循環参照を回避するように注意する必要があります。循環参照とは、オブジェクトAがオブジェクトBへの強い参照を保持し、それ自体が同じオブジェクトAへの強い参照を保持している場合です。この状況では、どちらのオブジェクトも割り当て解除されません。これは、Aがその参照カウンターの割り当てを解除されるためです。 0にデクリメントする必要がありますが、それらの参照の少なくとも1つはオブジェクトBです。オブジェクトBの割り当てを解除するには、その参照カウンタも0にデクリメントする必要がありますが、これらの参照の少なくとも1つはオブジェクトAです。問題を確認できますか? ARCは、さまざまなオブジェクト参照の処理方法についてプログラマーにヒントを与えることを可能にすることでこれを解決します。参照には、強参照と弱参照の2種類があります。強参照は、前述のように、参照されるオブジェクトの寿命を延ばすタイプの参照です(参照カウンタをインクリメントします)。弱参照は、オブジェクトの寿命を延ばさないタイプの参照です(つまり、オブジェクトの参照カウンタをインクリメントしない)が、参照されたオブジェクトの割り当てが解除され、ジャンクメモリを指す無効な参照が残されることを意味します。この状況を回避するために、オブジェクトが割り当て解除されると、弱参照は安全な値(Objective-Cではnilなど)に設定されます。したがって、オブジェクトには、すべての弱参照を追跡し、それらに設定するという特別な責任があります。自分自身を削除した後の安全な値。弱い参照は通常、子と親のオブジェクトの関係で使用され、親はそのすべての子オブジェクトへの強い参照を保持しますが、子オブジェクトは親への弱い参照を保持します。その理由は、ほとんどの場合、もう気にしない場合です。親オブジェクトの場合、おそらく子オブジェクトも気にしません。

ガベージコレクション(通常、単にガベージコレクションと呼ばれるもの)のトレースには、すべてのルートオブジェクト(つまり、グローバル変数、メインプロシージャのローカル変数などに格納されているオブジェクト)のリストの保持と、到達可能なオブジェクトのトレース(マーキングこれらのルートオブジェクトから検出された各オブジェクト)。ガベージコレクターがルートオブジェクトによって参照されるすべてのオブジェクトを通過すると、GCは割り当てられたすべてのオブジェクトを通過します。到達可能としてマークされている場合、メモリに残ります。到達可能としてマークされていない場合、割り当て解除されます。マークアンドスイープアルゴリズムとして。これには、循環参照の問題が発生しないという利点があります。相互参照されるオブジェクトAとオブジェクトBのどちらも、ルートオブジェクトから到達可能な他のオブジェクトによって参照されない場合、オブジェクトAもオブジェクトBも到達可能としてマークされず、両方とも割り当て解除されます。 。トレースガベージコレクターは一定の間隔で実行され、すべてのスレッドを一時停止します。これにより、一貫性のないパフォーマンス(散発的な一時停止)が発生する可能性があります。ここで説明するアルゴリズムは非常に基本的な説明です。最新のGCは通常、オブジェクト生成システム、3色セットなどを使用してはるかに高度であり、オブジェクトを隣接するストレージに移動することでプログラムのメモリ空間のデフラグなどの他のタスクも実行しますスペース、これが、C#やJavaなどのGC化された言語がポインターを許可しない理由です。ガベージコレクターのトレースの1つの重要な弱点は、クラスのデストラクターがもはや確定的ではないこと、つまりプログラマーであることです。オブジェクトが実際にガベージコレクションされる予定がいつであるかはわかりません。GCされた言語では、プログラマがクラスデストラクタを指定することさえできません。そのため、クラスを使用して、ファイルハンドルやデータベース接続などのリソースの管理をカプセル化できなくなります。 、など。開いているファイル、データベース接続を手動で閉じるのはプログラマの責任です。そのため、Javaなどの言語に、(try、catchブロックに)finallyキーワードがあり、掃除upコードは常にスタックが展開される前に実行されますが、C++(GCなし)では、このようなリソースはラッパーオブジェクト(スタックに割り当てられている)によって処理され、コンストラクターでリソースを取得してデストラクターで解放します。オブジェクトはスタックから削除されます。

パフォーマンスに関しては、どちらにもパフォーマンスのペナルティがあります。自動参照カウントは、より一貫したパフォーマンスを提供し、一時停止はありませんが、変数へのオブジェクトのすべての割り当て、オブジェクトのすべての割り当て解除などが参照カウンターの関連する増分/減分を必要とするため、アプリケーション全体が遅くなります。そして、弱参照の再割り当てと、割り当て解除される各オブジェクトの各デストラクタの呼び出しを処理します。オブジェクト参照を処理する場合、GCにはARCのパフォーマンスの低下はありません。ただし、ガベージ(リアルタイム処理システムでは使用できないレンダリング)を収集している間は一時停止が発生し、強制的に実行されないように効果的に機能して実行を一時停止しないようにするには、大きなメモリ領域が必要です。

両方に独自の長所と短所があることがわかるので、ARCの方が優れているか、GCの方が優れているかは明確ではありません。どちらも妥協点です。

PS: ARCは、参照カウンターのアトミックなインクリメント/デクリメントを必要とする複数のスレッド間でオブジェクトが共有される場合にも問題となり、それ自体がまったく新しい複雑さと問題の配列を示します。これは、「なぜだれもがガベージコレクションを使用するのか」という疑問に答えるはずです。

71
ALXGTV

インストラクターは間違っています。ガベージコレクションと参照カウントがどのように機能するかをよく理解している。

ガベージコレクションの場合、問題は、オブジェクトへの参照がどこかに残っている可能性があることです。以前の場所に関する情報への参照が配列に格納され、ガベージにならないために初期の自動運転車がクラッシュした場合があり、45分後にメモリが不足してクラッシュしました。文字通りではなく、運転を停止したが、墜落したかもしれない。

参照カウントでは、循環参照A-> B-> AまたはA-> B-> C-> ...-> Z-> Aがあり、参照カウントがゼロにならないという問題があります。そのため、弱い参照があり、それらをいつ使用するかを知る必要があります。

どちらの方法でも、物事がどのように機能するかを理解する必要があります。そうしないと、問題が発生します。パフォーマンスに関しては、Java=開発者にガベージコレクションの方が速いと言うと、Objective-Cの開発者がリファレンスカウンティングの方が速いと言います。違いは、言語を切り替えるのではなく、割り当ての数を減らすことです。

また、 弱い参照 についても知っておく必要があります。基本的には、オブジェクトを存続させないオブジェクトへの参照です。また、オブジェクトをスローする必要があると判断されたら、何が起こるかを正確に把握する必要があります。 in Java Objective-C/Swift参照カウントがゼロになると、そのオブジェクトは保持しようとするものに関係なく、移動します。(;;)の行を追加しない限り、dealloc/deinitメソッドに:-(

9
gnasher729

手動のメモリ管理、参照カウント、およびガベージコレクションには、それぞれ長所と短所があります。

  • 手動メモリ管理:非常に高速ですが、メモリの解放エラーが原因でバグが発生しやすくなります。また、単一のオブジェクトが存続し続ける必要のある複数のオブジェクトを取得した場合は、多くの場合、手動のメモリ管理に加えて、少なくとも参照カウントを自分で実装する必要があります。

  • 参照カウント:小さなオーバーヘッド(カウンターのインクリメント/デクリメントとゼロチェックはそれほど高価ではありません)により、非常に複雑なデータ構造の管理が容易になり、すべてのオブジェクトが他のオブジェクトによって参照される場合があります。欠点は、参照カウントでは参照が循環的でないことを要求することです。参照サークルを取得すると、メモリがリークします。

    弱い参照は、いくつかの参照サイクルを打破するために使用できますが、かなりの追加コストが伴います。

    1. 弱い参照は、弱い参照自体を管理するために2番目の参照カウントを必要とします。おそらく、弱参照は独立して割り当てる必要がある別のオブジェクトであり、メモリ消費量に大きなオーバーヘッドが発生します。

    2. 弱参照が存在する状態でオブジェクトを破棄するには、オブジェクトに属する弱参照をアトミックにリセットし、参照カウントをデクリメントする必要があります。そうしないと、弱参照の動作が不安定になります。詳細には詳しくありませんが、ロックフリーの方法でこれを実現するのは難しいと思います。

    これはすべて実行できますが、弱い参照がないと参照カウントほど簡単ではありません。

  • ガベージコレクション:考えられるすべての依存関係グラフに対応できますが、パフォーマンスにかなりの影響があります。結局、ガベージコレクターは、オブジェクトを収集する前に、オブジェクトに到達できないことを何らかの方法で証明する必要があります。現代のガベージコレクターは、作業中に長い遅延を回避するのに非常に優れていますが、作業は何らかの形で行う必要があります。これは、特定の時間枠内での応答を保証する必要があるリアルタイムアプリケーションでは特に悪いことです。

ご覧のとおり、3つの方法にはすべて最適な状況があります。手動での管理が簡単で、プログラムのパフォーマンスに厳しい制約がある場合は、手動でのメモリ管理が適しています。また、ガベージコレクションに対して参照カウントを使用できる限り、これはかなり高速であり、偽の遅延は発生しません。それでも、サイクルフリーの参照を保証できないため、ガベージコレクションを使用する必要がある場合があります。

すべてにガベージコレクションを使用する言語は、パフォーマンスに敏感なアプリケーションを許可するよりも、使いやすさが重要であると判断しました。