ガベージコレクション
Dはガベージ・コレクションをサポートするシステム・プログラミング言語である。 通常 メモリを明示的に解放する必要はない。必要に応じて割り当てればよい。 ガベージ・コレクタは定期的にすべての未使用メモリを利用可能なメモリ・プールに戻す。
Dはまた、ガベージ・コレクターが使用可能なコードを書くためのメカニズムも提供している。 がnot involved 。詳しくは以下を参照されたい。
明示的なメモリ割り当て管理に慣れたプログラマーは メモリの割り当てと メモリの割り当てと解放を明示的に管理することに慣れているプログラマーは、ガベージ・コレクションの利点と有効性に懐疑的だろう。 ガベージコレクションに懐疑的であろう。ガベージコレクションを念頭に置いて書かれた新しいプロジェクトと、既存のプロジェクトを ガベージコレクションを念頭に置いて書かれた新しいプロジェクトと、既存のプロジェクトをガベージコレクションに変換した経験の両方から、次のことがわかる。 ガベージコレクションは、次のことを示している:
- ガベージコレクションされたプログラムはしばしば速くなる。これは
直感に反するが、その理由は以下の通りだ:
- 参照カウントは、明示的なメモリ割り当て問題を解決するための一般的なソリューションである メモリ割り当て問題を解決するための一般的なソリューションである。割り当てが行われるたびに 割り当てが行われるたびにインクリメントとデクリメントを実行するコードは、速度低下の原因の1つである。 速度低下の原因のひとつだ。これをスマート・ポインタ・クラスの後ろに隠しても、スピードは上がらない。 スピードは上がらない。(参照カウント・メソッドは一般的な解決策ではない 循環参照は削除されないからだ)。
- デストラクタは、オブジェクトが獲得したリソースの割り当てを解除するために使われる。 ほとんどのクラスでは、このリソースは割り当てられたメモリーである。 ガベージコレクションによって、ほとんどのデストラクタは空になり、完全に破棄することができる。 完全に破棄することができる。
- メモリーを解放するデストラクターは、オブジェクトがスタックに割り当てられたとき、重要な意味を持つ。 オブジェクトがスタックに割り当てられると、これらのデストラクタがすべてメモリを解放することになる。それぞれについて、例外が発生したときに 例外が発生した場合、デストラクタはすべて各フレームで呼び出され、保持しているメモリを解放する。 が各フレームで呼び出され、保持しているメモリを解放する。もし デストラクタが無関係になれば、例外を処理するために特別なスタックフレームを設定する必要はなくなる。 例外を処理するために特別なスタック・フレームを設定する必要がなくなり、コードの実行が速くなる。
- ガベージコレクションは、メモリが逼迫したときにのみ実行される。メモリが逼迫していないときは メモリが逼迫していない場合、プログラムはフルスピードで実行され、トレースや解放に時間を費やすことはない。 メモリのトレースと解放に時間を費やすことはない。
- ガベージコレクションされたプログラムは、メモリリークの蓄積による漸進的な劣化に悩まされることはない。 メモリ・リークの蓄積による漸進的な悪化に悩まされることはない。
- ガベージコレクタは未使用のメモリを再利用する。 そのため、ガベージ・コレクションは未使用のメモリを再利用する。 メモリ・リーク」は発生しない。GCプログラム は長期的に安定している。
- ガベージコレクションされたプログラムには、見つけにくいポインタのバグが少ない。これは これは、解放されたメモリへのダングリング参照がないからである。明示的にメモリを管理する したがって、そのようなコードにはバグがない。
- ガベージコレクションされたプログラムは開発とデバッグが速い。 なぜなら、開発、デバッグ、テスト、メンテナンスの必要がないからだ。 明示的な割り当て解除コードを開発、デバッグ、テスト、保守する必要がないからだ。
ガベージコレクションは万能ではない。デメリットもある:
- GCがいつメモリを確保するかは必ずしも明らかではない。 そのため、プログラムが予期せず中断することがある。
- コレクションが完了するまでの時間には制限がない。 実際には非常に速いが、通常は保証できない。
- 通常、コレクター・スレッド以外のすべてのスレッドは、コレクションが進行している間は停止しなければならない。 停止しなければならない。
- ガベージ・コレクタは、明示的なデアロケータが保持しないメモリを保持することができる。 ガベージ・コレクタは、明示的なデアロケータでは保持できないメモリを保持することができる。
- ガベージコレクションは基本的なオペレーティングシステムとして実装されるべきである。 システムとして実装されるべきである。 カーネルサービスとして実装されるべきである。しかしそうではないので、ガベージコレクションプログラムはガベージコレクションの実装を持ち歩かなければならない。 ガベージコレクションの実装を持ち歩かなければならない。これは 共有ライブラリにすることもできるが、それでも存在する。
このような制約には、メモリ管理 Dが提供するメカニズムを含む。 Dで提供されるGCヒープ外の割り当てを制御するメカニズムによって対処される。
現在、ランタイム・ライブラリをGCヒープ割り当てから解放するための作業が進行中である、 GCインフラストラクチャを使用できないシナリオでも使用できるようにするためである。
ガベージコレクションの仕組み
GCの仕組みはこうだ:
- 現在GCメモリを割り当てようとしているスレッド以外のすべてのスレッドを停止する。 GCメモリを割り当てようとしているスレッド以外のすべてのスレッドを停止する。
- GC作業のために現在のスレッドを「ハイジャック」する。
- すべての「ルート」メモリ範囲をスキャンして、GCに割り当てられたメモリへのポインタを探す。 GCに割り当てられたメモリをスキャンする。
- ルートが指す割り当て済みメモリを再帰的にスキャンする。 ルートが指すすべての割り当て済みメモリを再帰的にスキャンし、GC割り当て済みメモリへのさらなるポインタを探す。
- GCに割り当てられたメモリのうち、アクティブなポインタがなく、破壊を必要としないメモリをすべて解放する。 デストラクタを実行する必要がない。
- デストラクタを実行する必要のある、すべての到達不可能なメモリをキューに入れる。
- 他のすべてのスレッドを再開する。
- キューに入れられたすべてのメモリに対してデストラクタを実行する。
- 残りの到達不能メモリを解放する。
- 現在のスレッドを、それまで行っていた作業に戻す。
ガベージコレクションされたオブジェクトと外部コードとの連携
ガベージ・コレクターは、ルーツを探す:
- 静的データセグメント
- 各スレッドのスタックとレジスタの内容
- 各スレッドのTLS(スレッドローカルストレージ)領域
- core.memory.GC.addRoot()またはcore.memory.GC.addRange()によって追加されたルート。
オブジェクトへの唯一のポインタ オブジェクトへの唯一のポインタがこれらの領域の外に保持されている場合、コレクタはそれを見逃してメモリを解放する。 メモリを解放する。
このような事態を避けるには
- コレクタがポインタをスキャンする領域にオブジェクトへのポインタを保持する。 にオブジェクトへのポインタを保持する;
- core.memory.GC.addRoot()を使用して、オブジェクトへのポインタが格納されるルートを追加する。 またはcore.memory.GC.addRange()を使用してオブジェクトへのポインタが格納されているルートを追加する。
- 外部コードのストレージを使用して、オブジェクトを再割り当てしてコピーする。 アロケータ またはCランタイム・ライブラリのmalloc/freeを使用する。
ポインタとガベージ・コレクター
D言語のポインターは大きく2つのカテゴリーに分けられる:それは ガベージコレクションされたメモリを指すものと、そうでないものである。後者の例:」である。 後者の例としては、Cのmalloc()の呼び出しによって生成されるポインタ、Cのライブラリ・ルーチンから受け取るポインタ、静的データへのポインタ、ガベージコレクションされないメモリを指すポインタなどがある。 静的データへのポインタ、スタック上のオブジェクトへのポインタ、 スタック上のオブジェクトへのポインタなどである。これらのポインタに対しては、Cで合法なことは何でもできる。 これらのポインタでは、C言語で合法なことは何でもできる。
しかし、ガベージコレクションされたポインタと参照については、次のようなものがある。 いくつかの 制限がある。これらの制限は些細なものであるが、ガベージコレクタの設計に最大限の柔軟性を持たせることを意図している。 ガベージコレクタの設計に最大限の柔軟性を持たせるためのものである。
- ポインタを他の値とxorしてはならない。 Cで使われるリンクリストのトリックのように、ポインタを他の値とxorしてはいけない。
- xorトリックを使って2つのポインタの値を入れ替えないこと。
- キャストやその他のトリックを使って、ポインタを非ポインタ変数に格納しないこと。
などのトリックを使わないこと。
void* p; ... int x = cast(int)p; // エラー: 未定義の動作
ガベージコレクタはGCポインタのために非ポインタフィールドをスキャンしない。 - ポインタのアライメントを利用して、ビット・フラグを低次ビットに格納しないこと。
を低次ビットに格納してはならない:
p = cast(void*)(cast(int)p | 1); // エラー: 未定義の動作
- ガベージコレクションされたヒープを指す可能性のある値をポインタに格納しない。
を指す可能性のある値をポインタに格納してはならない:
p = cast(void*)12345678; // エラー: 未定義の動作
コピーしたガベージコレクタがこの値を変更することがある。 - null 以外のマジック値をポインタに格納しないこと。
- ポインタ値をディスクに書き出し、再び読み込まないこと。 再度読み込まないこと。
- ポインタ値を使ってハッシュ関数を計算しないこと。コピー ガベージ・コレクターは、メモリ上でオブジェクトを任意に移動させることができる、 そのため 計算されたハッシュ値を無効にしてしまう。
- ポインタの順序に依存しないこと:
if (p1 < p2) // エラー: 未定義の動作 ...
ガベージ・コレクタはオブジェクトをメモリ内で移動させることができるからだ。 メモリの中でオブジェクトを移動させることができるからだ。 - ポインタにオフセットを加算したり減算したりしてはならない。
ガベージコレクタが最初に割り当てたオブジェクトの境界の外側を指すような、ポインタのオフセットの加算や減算を行わないこと。
を割り当てないこと。
char* p = new char[10]; char* q = p + 6; // OK q = p + 11; // エラー: 未定義の動作 q = p - 1; // エラー: 未定義の動作
- ポインタがGCヒープを指す可能性がある場合は、ポインタのアライメントを誤らないこと。
など、GCヒープを指す可能性がある場合は、ポインタをずらさないこと:
struct Foo { align (1): byte b; char* p; // ポインタがずれている }
基本的なハードウェアがサポートしていれば、ミスアラインポインタを使用することもできる。 and 、そのポインタはGCヒープを指すために使われることはない。 を指すために使われることはない。 - ポインタの値をコピーするために、バイト単位のメモリコピーを使用しないこと。 これは、有効なポインタが存在しない中間状態を引き起こす可能性がある。 このような状態でGCがスレッドを一時停止すると、メモリが破損する可能性がある。 このような状態でGCがスレッドを一時停止すると、メモリが破損する可能性がある。 ほとんどのmemcpy() の実装は動作する。 のほとんどの実装は動作する。 しかし、このような実装はC標準では保証されていない。 このような実装はC標準では保証されていないので、使用には十分な注意が必要である。 memcpy() は細心の注意を払って使用すること。
- 構造体インスタンス内に、同じインスタンスを指すポインタを持たないこと。 を指すポインタを持たないこと。この場合の問題は、インスタンスがメモリ内で移動した場合、ポインタは インスタンスがメモリ内で移動された場合、ポインタは元の場所を指すことになる。 悲惨な結果になる可能性が高い。
信頼でき、実行できることだ:
- ポインタとストレージを共有するために共用体を使う:
union U { void* ptr; int value }
- ガベージコレクションされたオブジェクトの開始点へのポインタは、オブジェクトの内部へのポインタが存在すれば保持する必要はない。
オブジェクトの内部へのポインタが存在する場合は、そのポインタを保持する必要はない。
char[] p = new char[10]; char[] q = p[3..6]; // オブジェクトを保持するにはqで十分である // pも保持する必要はない。
たいていの作業では、ポインターを使うことは避けられる。Dは 機能 参照オブジェクトのような、ほとんどの明示的なポインタの使用を時代遅れにする機能を提供している。 オブジェクトを参照する、 動的配列、ガベージコレクションなどである。ポインタ ポインターは、C言語のAPIとのインターフェイスを成功させるためと、いくつかの低レベルの作業のために提供されている。 いくつかの低レベルの作業のために提供されている。
ガベージ・コレクターを使う
ガベージコレクションは、すべてのメモリ解放問題を解決するわけではない。 例えば 例えば、大きなデータ構造へのポインタが保持されている場合、ガベージ・コレクションは、それが二度と参照されないとしても、それを取り戻すことはできない。 たとえそれが二度と参照されないとしても、ガベージコレクタはそれを取り戻すことはできない。この この問題を解決するには、オブジェクトへの参照やポインタをNULLに設定するのが良い方法だ。 ポインターをNULLに設定するのがよい方法である。
このアドバイスは、静的参照または他のオブジェクトの中に埋め込まれた参照にのみ適用される にのみ適用される。スタックに格納された参照をNULLにする意味はあまりない。 新しいスタック・フレームが初期化されるからだ。
オブジェクトのピン止めと動くガベージ・コレクター
Dは現在、移動ガベージ・コレクタを使用していないが、上記のルールに従うことで、ガベージ・コレクタを実装することができる。 を実装することができる。特別な動作は必要ない オブジェクトを固定する。移動コレクタは、曖昧な参照がなく、参照を更新できるオブジェクトだけを移動する。 曖昧な参照がなく、それらの参照を更新できるオブジェクトだけを移動する。 他のすべてのオブジェクトは、自動的に固定される。
Dガベージ・コレクターに関わる操作
コード・セクションによっては、ガベージ・コレクターの使用を避ける必要がある場合がある。 以下の構文は、ガベージ・コレクターを使ってメモリを確保する可能性がある:
- NewExpression
- 配列の追加
- 配列の連結
- 配列リテラル(静的データの初期化に使われる場合を除く)
- 連想配列リテラル
- 連想配列の挿入または削除
- 連想配列からキーや値を取り出す。
- 以下のようなネストされた関数のアドレスを取る(つまり、デリゲートする)。 外部スコープの変数にアクセスする
- 外部スコープの変数にアクセスする関数リテラル。
- 条件に失敗したAssertExpression
ガベージ・コレクターを設定する
バージョン2.067から、ガベージ・コレクターはコマンドライン、環境、または組み込まれたオプションによって設定できるようになった。 コマンドライン、環境、または実行ファイルに組み込まれたオプションによって によって設定できるようになった。
デフォルトでは、GCオプションは実行するプログラムのコマンドラインにのみ渡すことができる。 のコマンドラインにのみ渡すことができる。
app "--DRT-gcopt=profile:1 minPoolSize:16" arguments to app
利用可能なGCオプションは以下の通りである:
- disable:0|1 - 開始を無効にする。
- profile:0|1 - プログラム終了時にサマリーを表示するプロファイリングを有効にする。
- gc:conservative|precise|manual - GCの実装を選択する(デフォルトはconservative)
- initReserve:N - 予約する初期メモリをMB単位で指定する。
- minPoolSize:N - 初期および最小プールサイズ(MB単位
- maxPoolSize:N - MB単位の最大プールサイズ
- incPoolSize:N - プールサイズの増分 MB
- parallel:N - マーキング用の追加スレッド数
- heapSizeFactor:N - 目標とするヒープサイズと使用メモリの比率
- cleanup:none|collect|finalize - 終了時に生きているオブジェクトをどう扱うか。
- collect: コレクションを実行する(後方互換性のためのデフォルト)
- none: 何もしない。
- finalize: すべてのライブオブジェクトを無条件にファイナライズする。
さらに、--DRT-gcopt=helpを指定すると、オプションのリストと現在の設定が表示される。
DRT-」で始まるコマンドラインオプションは、main、 で始まるコマンドラインオプションは、mainを呼び出す前にフィルタリングされるため、プログラムには表示されない。これらのオプションは、rt_args 。
コマンドラインからの設定は、ランタイムからのデフォルトを使う前に、リンカーが拾う変数を宣言することで無効にできる。 を宣言することで、コマンドラインからの設定を無効にすることができる:
extern(C) __gshared bool rt_cmdline_enabled = false;
同様に、rt_envvars_enabled をブーリアンとして宣言し、環境変数 を使ってコンフィギュレーションできるようにする。 環境変数DRT_GCOPT :
extern(C) __gshared bool rt_envvars_enabled = true;
実行ファイルにデフォルトのコンフィギュレーション・プロパティを設定するには、次のように指定する。 rt_options という名前のオプションの配列を指定する:
extern(C) __gshared string[] rt_options = [ "gcopt=initReserve:100 profile:1" ];
オプションの評価順序は、rt_options 、次に環境変数、そしてコマンドライン引数の順である。 つまり、コマンドライン引数が無効になっていなければ、コマンドライン引数は環境変数や実行ファイルに組み込まれたオプションよりも優先される。 つまり、コマンドライン引数が無効化されていない場合、コマンドライン引数は、環境変数を通して指定されたオプションや、実行ファイルに組み込まれたオプションを上書きすることができる。
正確なヒープ・スキャン
上記のオプションでガベージコレクタとしてprecise を選択することは、型情報を使用することを意味する。 情報は、ヒープに割り当てられたデータ・オブジェクト内の実際の、あるいは可能性のあるポインタや参照を識別するために使われる。 参照を識別するために使用される。ポインタでないデータは ポインタでないデータは「偽ポインタ」として他のメモリへの参照と解釈されない。コレクターは コレクタは、メモリスロットがポインタと整数値の両方を含む可能性がある場合、悲観的な仮定をしなければならない。 整数値の両方を含むことができる場合、コレクタは悲観的な仮定をしなければならない(例えば、union )。
のGCメモリー関数を使うには、以下のようにする。core.memory のGCメモリ関数をポインタと非ポインタのデータが混在するデータに対して使用するには、オプションのパラメータとして、割り当てられた構造体、クラス、または型のTypeInfoを渡す。 をオプションのパラメータとして渡す。 デフォルトのnull は、あらゆる場所にポインタを含む可能性のあるメモリとして解釈される。
struct S { size_t hash; Data* data; } S* s = cast(S*)GC.malloc(S.sizeof, 0, typeid(S));
注意: 正確なスキャンを可能にするには、若干の注意が必要である:正確なスキャンを可能にするには、型宣言に若干の注意が必要である。 型宣言には少し注意が必要である。例えば、構造体の一部としてバッファを確保し、後でこのメモリに他の割り当てを参照するオブジェクト・インスタンスを配置する場合である。 他の割り当てへの参照を持つオブジェクト・インスタンスをこのメモリーに配置する、 基本整数型を使って領域を確保してはならない。そうすると ガベージコレクタが参照を検出しなくなる。代わりに、この領域を保守的にスキャンする配列型を使う。 を使う。void* 。 GCによってスキャンされるポインタの適切なアライメントを保証する。
DATAとTLSセグメントの正確なスキャン
Windowsのみ: バージョン2.075から、実行ファイルのDATA(グローバル共有データ)と と TLS セグメント (スレッドローカルデータ) を正確にスキャンするように設定できる。 または DLL の DATA (グローバル共有データ) と TLS セグメント (スレッドローカルデータ) は、ガベージコレクタによって正確にスキャンされるように設定できる。 が正確にスキャンするように設定できる。これは コンパイラが発する情報を利用する。 セグメント内の変更可能なポインターを特定する。不変ポインタ 初期化子 を持つ不変ポインタもスキャンから除外される。
正確なスキャンは、Dランタイムオプション "scanDataSeg"で有効にできる。可能なオプション 値は "conservative"(デフォルト)と"precise"である。GCオプションと同様に、コマンドライン、環境、または実行ファイルに埋め込むことができる。 コマンドライン、環境、または実行ファイルに埋め込むことができる。
extern(C) __gshared string[] rt_options = [ "scanDataSeg=precise" ];
注意:正確なスキャンを可能にするには、タイピングに若干の注意が必要である。 グローバル・メモリを使用する。例えば、DATA/TLSセグメントにあらかじめメモリを確保し、後でそのメモリに他のメモリを参照するオブジェクト・インスタンスを配置する。 このメモリーに、他の割り当てを参照するオブジェクト・インスタンスを配置する、 基本整数型を使って領域を確保してはならない。そうすると ガベージコレクタが参照を検出しなくなる。代わりに、この領域を保守的にスキャンする配列型を使用する。 を使う。void* 。 GCによってスキャンされるポインタの適切なアライメントを保証する。
class Singleton { void[] mem; } align(__traits(classInstanceAlignment, Singleton)) void*[(__traits(classInstanceSize, Singleton) - 1) / (void*).sizeof + 1] singleton_store; static this() { emplace!Singleton(singleton_store).mem = allocateMem(); } Singleton singleton() { return cast(Singleton)singleton_store.ptr; }その領域を正確に型付けするには、コンパイラにクラスのインスタンスをDATAセグメントに生成させる。 インスタンスをDATAセグメントに生成させる:
class Singleton { void[] mem; } shared(Singleton) singleton = new Singleton; shared static this() { singleton.mem = allocateSharedMem(); }しかし、これはTLSメモリーには使えない。
並列マーキング
デフォルトでは、ガベージコレクタはヒープをマークするために利用可能なすべてのCPUコアを使用する。
これは、収集のマーク段階で中断されないスレッドがある場合、アプリケーションに影響を与える可能性がある。 を停止していないスレッドがある場合、アプリケーションに影響を与える可能性がある。GCオプションで GCオプションparallel 、 例えば、コマンドラインで--DRT-gcopt=parallel:2 を渡すか、バイナリにオプションを埋め込む。 ライン上で渡すか、rt_options を介してバイナリにオプションを埋め込む。 実際に作成されるスレッド数は、以下のように制限される。 core.cpuid.threadsPerCPU-1. 0 の値は、並列マーキングを完全に無効にする。
独自のガベージ・コレクタを追加する
GCの実装はレジストリに追加される。 レジストリに追加される。 バイナリーにリンクするだけで、より多くの実装を提供することができる。そのためには、次の関数を追加する。 pragma(crt_constructor) Dランタイムの初期化の前に実行される関数を追加する:
import core.gc.gcinterface, core.gc.registry; extern (C) pragma(crt_constructor) void registerMyGC() { registerGCFactory("mygc", &createMyGC); } GC createMyGC() { __gshared instance = new MyGC; instance.initialize(); return instance; } class MyGC : GC { /*...*/ }
[インターフェイス(gc.interface)と登録(gc.registry)を定義する GC モジュールは、現在公開されていない。 (gc.registry)を定義するGCモジュールは、現在公開されていない。 バージョンによって変更される可能性がある。インポート検索パスを druntime/srcパスにインポート検索パスを追加し、例をコンパイルする]。
新しいGCは、利用可能なガベージコレクタのリストに追加される。 新しいGCは、通常の設定オプションで選択できる利用可能なガベージコレクタのリストに追加される。 rt_options をバイナリに組み込むなどして選択できる:
extern (C) __gshared string[] rt_options = ["gcopt=gc:mygc"];
静的にリンクされたバイナリからの標準GC実装は、関数 を再定義することで削除できる。 関数extern(C) void* register_default_gcs() を再定義することで、静的にリンクされたバイナリから標準のGC実装を削除することができる。 カスタムガベージコレクタが登録されていない場合 カスタム・ガベージ・コレクタが登録されていない場合、GCで管理されたメモリを割り当てようとすると、適切なメッセージが表示されてアプリケーションが終了する。 される。
参考文献
DEEPL APIにより翻訳、ところどころ修正。
このページの最新版(英語)
このページの原文(英語)
翻訳時のdmdのバージョン: 2.108.0
ドキュメントのdmdのバージョン: 2.109.1
翻訳日付 :
HTML生成日時:
編集者: dokutoku