12

なぜサブ文字列がC#でO(n)を取るのかについてのこの人気のある質問では、提供された主な回答の1つは、大きな配列が割り当てられ、新しい文字列が配列の小さなスライスを参照するだけでサブ文字列が計算された場合、ガベージコレクターは元の文字列が参照されなくなった場合でも、より大きな文字列を含む文字の配列を再利用することはできません。

これは完全に有効な答えのように見えますが、理論的には、アレイのガベージコレクターを構築して、まだ使用されている小さなサブアレイを残しながら、ほとんどのアレイをガベージコレクションできるようにすることができるようです。言い換えると、50,000要素の配列があり、そのうち100要素の小さなスライスだけがまだ使用されている場合、ガベージコレクターは、配列を3つの部分(100要素のスライスの前の要素、100要素の要素)に分割できます。それ自体をスライスし、100要素のスライスの後の要素をスライスします。次に、これらのピースの最初と最後をガベージコレクションします。

私の質問は、言語実装が実際にこの種のガベージコレクターを使用するのか、それとも理論上のみ存在するのかということです。このようなガベージコレクターを持つ言語実装の例を知っている人はいますか?

4

5 に答える 5

6

理論的には、はい...可能です。しかし、GC には問題があります。ガベージを収集するには、メモリに格納されているデータのレイアウトを知る必要があり、メモリ ブロックが使用されているかどうかを示すデータも格納する必要があります...しかし、レイアウトランタイムは型キャストを行うためにオブジェクトの型 (つまり、メモリ レイアウト) を知る必要があるため、情報はランタイムと共有されます。

GC はどのように機能しますか?

GC は、認識しているルート オブジェクトの読み取りを開始します。すべての参照を取得し、それらをin-useとしてマークします。これらの参照されたオブジェクトごとに、レイアウトを取得し、これらのオブジェクトからさらに参照を読み取り、使用中としてマークします...そして、参照がなくなるまでこのプロセスが続きます。

注: タイプ情報とレイアウト情報を同じ意味で使用しています。

例:

Imagine we have some object layouts:
====================================
A: { int, object, double, object }
B: { object, object }
C: { int }


Memory Data:
============
Now we need to describe what is in memory.
The following list is composed of memory positions,
and the data contained in each position.
There are 2 kinds of notation I will use:
  - Address: ROOT[ list-of-root-references ]
  - Address: type-identifier { object-data }
     Note that each object can span multiple memory
     positions from the first one.
     e.g. 90: B { 0, 0 } -- this reads as
       "stored at address 90: type-identifier of B followed by raw data { 0, 0 }"
  - A reference is represented by a number,
    that point to the memory address of the pointed object.

 1: ROOT[ 90, 20, 30 ]
20: A { 1236, 30, 0.5, 60 }
30: B { 60, 70 }
40: C { 1237 }
50: A { 1238, 20, 0.8, 50 }    There is a self-reference here!
60: C { 1234 }
70: A { 1234, 0, 0.7, 80 }
80: C { 1235 }
90: B { 0, 0 }                 Note that 0 is a null reference!

   The GC need to know the layout of each object.
   Otherwise it wouldn't be abled to tell
   what knid of information is stored in each memory position.

Running the GC:
===============
Garbage collecting steps, to clean the memory described above:
Step 1: Get ROOT references: 2, 3, 9 and mark as 'in-use'
Step 2: Get references from 2, 3 and 9: 3, 6, 7. Note that 0 is a null reference!
Step 3: Get references from 3, 6 and 7: 6, 7, 8, and mark them.
Step 4: Get references from 6, 7, 8, and mark them: only 8!
Step 5: Get references from 8... it has none! We are finished with marking objects.
Step 6: Delete unmarked objects.


      This shows what happened in each step with each object stored in the memory.

Step ->  1  2  3  4  5
Memory                    
20       x           
30       x  x        
40                   DELETE
50                   DELETE
60          x  x     
70          x  x     
80             x  x  
90       x           

私が説明したのは、非常に基本的な GC アルゴリズムです。

トライカラーのマーキングを見てください...それは本当に素晴らしいです! これが、実際の最新の GC の作成方法です。

ガベージ コレクション (コンピューター サイエンス) - いくつかの最新の GC 方法論について説明します。

しかし... レイアウトに関する情報はどこに保存されているのでしょうか?

GC とランタイムの両方に影響するため、この質問は重要です。高速な型キャストを行うには、型情報を参照の近く、または割り当てられたメモリの近くに配置する必要があります。割り当てられたメモリ ブロックのカタログに型情報を格納することも考えられますが、その場合は、オブジェクトを削除する必要がある場合に new 演算子や GC と同じように、型キャストがカタログにアクセスする必要があります。

参照の近くにレイアウト情報を格納すると、同じオブジェクトへのすべての参照で、ポインタ自体とともに同じ情報が繰り返されます。

例:

To represent the memory data I will introduce the following notation:
 - Address: { object-data } -- this time object type is not at the begining!
 - A reference is represented by a type-identifier and an address-number,
   that point to the memory address of the pointed object,
   in the following format: type/number...
   e.g. A/20 -- this reads as: "at address 20, there is an object of type A"
Note that: 0/0 is a null reference,
      but it still requires space to store the type.

The memory would look like this:
 1: ROOT[ B/90, A/20, B/30 ]
20: { 1236, B/30, 0.5, C/60 }
30: { C/60, A/70 }
40: { 1237 }
50: { 1238, A/20, 0.8, A/50 }
60: { 1234 }
70: { 1234, 0/0, 0.7, C/80 }
80: { 1235 }
90: { 0/0, 0/0 }

割り当てられたメモリ ブロックの近くにレイアウト情報を格納する場合は、それで問題ありません。高速で、レイアウト情報の繰り返しを回避します。

例:

The memory looks like the first sample:
 *This is the same notation used at the begining of this answer.
 1: ROOT[ 90, 20, 30 ]
20: A { 1236, 30, 0.5, 60 }
30: B { 60, 70 }
40: C { 1237 }
50: A { 1238, 20, 0.8, 50 }
60: C { 1234 }
70: A { 1234, 0, 0.7, 80 }
80: C { 1235 }
90: B { 0, 0 }

これまでのところ、とてもいいです...しかし、今は共有メモリが必要です。

最初に気付くのは、割り当てられたメモリの近くにレイアウト情報を保存できなくなったことです。

共有メモリを持つ配列を想像してください。

例:

 I'll introduce a new notation for arrays:
    type-identifier < array-length >

 1: ROOT[ 20, 31 ] -- pointer 31 is invalid, destination has no type-identifier.
20: INT<5>  -- info about layout of the next memory data (spans by 10 memory units)
30:  2
31:  3 -- should have type-identifier, because someone
32:  5              is pointing here!! Invalid memory!!
33:  7
34: 11

代わりに、ポインターの横にレイアウト情報を配置することもできます。共有メモリ配列が可能になりました:

例:

 1: ROOT[ INT<5>/30, INT<2>/31 ]
30:  2
31:  3
32:  5
33:  7
34: 11

このアプローチでは、どこでもレイアウト情報を繰り返さなければならないことを思い出してください...しかし、ここでのポイントは、使用するメモリを少なくすることですよね??? ただし、メモリを共有するには、レイアウト データ/ポインタを格納するためにより多くのメモリが必要です。ドーナツはまだありません。=(

希望は 1 つだけです。実行時の機能を低下させましょう!

これが私の答えです-どのようにそれが可能だと思います=>

メモリ割り当てカタログを使用して型情報を保存するのはどうですか?

これを行うことはできますが、その場合、動的キャストが影響を受け、GC 自体も影響を受けます。GC はオブジェクトを削除するためだけにメモリ カタログにアクセスする必要があると説明したことを思い出してください。これで、単に削除するだけでなく、参照を見つけるたびにカタログにアクセスする必要があります。ああ、神様!!これにより、GC のパフォーマンスが低下し、ランタイムのパフォーマンスも低下します。コストが高すぎると思います!

<= これが私の答えです

しかし...ランタイムが動的キャストをサポートしていない場合はどうなりますか? コンパイラがコンパイル時に型に関するすべてを知っている場合... GCは存在しません...型に関する情報が必要です.

簡単でスマートなソリューションは見えません。

たぶん私はただ間違っているだけです。しかし、これまで以上に良い方法を想像することはできません。最新の GC はこれよりもさらに複雑です... ここでは基本的なことだけを取り上げましたが、最新の GC は他の方法、つまり他のより信頼性の高い方法で最適化していると思います。

その他の参照:

http://en.wikipedia.org/wiki/Garbage_collection_(computer_science)

http://www.memorymanagement.org/glossary/t.html

http://www.cs.purdue.edu/homes/sbarakat/cs456/gc-2.pdf

Tri-Color Incremental Updating GC: 各スタックを 2 回スキャンする必要がありますか?

于 2011-08-12T04:31:23.630 に答える
3

D言語は、まさにこの種のGC動作を備えた配列スライスをサポートします。詳細については、 http: //www.dsource.org/projects/dcollections/wiki/ArrayArticle#WhosResponsibleを参照してください。

于 2011-08-13T23:42:41.490 に答える
1

このようなガベージ コレクターを持つ言語実装の例を知っている人はいますか?

いいえ。現在、これを行う言語実装はないと思います。

実際の言語が行う最も近いことは何ですか? 関数型言語は、多くの場合、フラット配列を階層ツリーに置き換えます。そのため、大きな文字列はロープと呼ばれるデータ構造で表されます。プログラムがロープから部分文字列を取得する場合、ロープ内でまだ到達可能なすべてのデータをコピーする必要なく、ほとんどの文字列を収集できます。ただし、このような機能的なデータ構造は、フラットな配列よりもはるかに遅くなります。

なぜ私たちはこれをしないのですか?おそらく、これらの種類のアプローチは多くの複雑さをもたらし、比較的重要でない問題を解決するという認識があるためです (私は、スライスがあまりにも多くのスペースを到達可能に保つという問題を経験したことがありません)。また、内部ポインターを禁止する GC を使用する伝統があり、その結果、GC はスライスを理解できません。特に、プロダクション GC はすべて、オブジェクトごとのメタデータを、ヒープに割り当てられたメモリ ブロックの先頭のヘッダーに配置します。

私は実際に、代わりにクワッドワード参照を使用してヘッダーを回避する VM ( HLVM ) を作成しました。参照カウントの調査から、大多数のオブジェクトの参照カウントは 1 であることがわかっているため、参照ごとのヘッダーの重複の可能性は、実際には想像以上に安価です。ヘッダーがないということは、配列の HLVM の内部表現が C 互換であることを意味するため、相互運用性がはるかに簡単かつ高速になります。同様に、スライスは配列の中央への参照になる可能性があります。マーク領域などの適切な GC アルゴリズムは、ヒープに割り当てられたメモリ ブロックの到達不可能な部分を解放し、到達可能なスライスを保持しながら再利用することができます。

別の解決策は、仮想メモリを使用することです。マッピング論理ページを同じ物理ページに「偽造」してコピーすると、GC が到達不能ページを収集する可能性があります。ただし、これは非常に粗粒度であり、その結果、さらにニッチです。

于 2012-06-19T14:26:45.313 に答える
0

確かに、「よりスマートな」ガベージ コレクタを作成することの危険性は常に、何らかの方法でユーザーを不自由にすることであり、コードが機能しなくなったり、過度に熱心なガベージ コレクタのハックな回避策になったりします。

于 2011-08-08T08:31:24.750 に答える
0

連想配列の実装 (PERL、PHP、javascript など) はすべて、この方法で行う必要があると思います。これを「ガベージ コレクション」と呼びますが、これは、ガベージ コレクターが未使用であることを知るために、特定のアイテムを最初に設定解除 (削除、削除) する必要があることを意味します。したがって、これは通常の削除/削除/設定解除であり、連想配列だけでなく、特定の言語実装で使用されるデータ構造にも確実に反映されます。そうしないと、ほとんど空の配列によってメモリが使い果たされる可能性があります...

于 2011-08-08T16:42:39.013 に答える