ホームページ >php教程 >php手册 >PHP5.3 のガベージ コレクション メカニズム (動的ストレージ割り当てスキーム) についての深い理解

PHP5.3 のガベージ コレクション メカニズム (動的ストレージ割り当てスキーム) についての深い理解

WBOY
WBOYオリジナル
2016-06-13 11:56:33849ブラウズ

ガベージ コレクション メカニズムは、動的なストレージ割り当てスキームです。プログラムで不要になった割り当てられたメモリ ブロックを自動的に解放します。 メモリを自動的に再利用するプロセスは、ガベージ コレクションと呼ばれます。ガベージ コレクション メカニズムにより、プログラマはプログラム メモリの割り当てについてあまり心配する必要がなく、ビジネス ロジックにより多くのエネルギーを注ぐことができます。 現在人気のあるさまざまな言語の中でも、ガベージ コレクション メカニズムは新世代の言語に共通の機能です。たとえば、Python、PHP、Eiffel、C#、Ruby などはすべてガベージ コレクション メカニズムを使用しています。 ガベージ コレクションは現在一般的な行為ですが、それはもはや若いものではありません。 1960 年代には MIT によって開発された Lisp システムに存在していましたが、当時の技術的条件が未熟だったため、1990 年代に Java が登場するまで、ガベージ コレクション メカニズムは一見美しいテクノロジになりました。収集メカニズムは広く使用されています。

PHP は言語層でのメモリの動的管理も実装しています。これについては、前の章で詳しく説明しましたが、開発者は煩雑なメモリ管理から解放されます。これに加えて、PHP は言語層でガベージ コレクション メカニズムも提供するため、プログラマはプログラムのメモリ割り当てについてあまり心配する必要がなくなります。

PHP5.3 より前の PHP には、参照カウントに基づく単純なガベージ コレクションしかありませんでした。変数の参照カウントが 0 になると、PHP はメモリ内の変数を破棄しますが、ここでのガベージをガベージと呼ぶことはできません。 そして、PHP は、ライフサイクルの終了後にこのプロセス/スレッドによってクリックされたコンテンツを解放します。この方法により、PHP は初期段階であまり多くのメモリ リークを考慮する必要がないと判断されます。 しかし、PHP の発展、PHP 開発者の増加、および PHP が扱うビジネス範囲の拡大に伴い、PHP5.3 ではより完全なガベージ コレクション メカニズムが導入されました。 新しいガベージ コレクション メカニズムは、サイクルを処理できない参照メモリ リークの問題を解決します。 PHP5.3 のガベージ コレクション メカニズムは、記事の「参照カウント システムにおける同時サイクル コレクション」の同期アルゴリズムを使用します。このアルゴリズムの導入については詳しく説明しません。PHP の公式ドキュメント「Collecting Cycles」に図解による導入があります。
前述したように、PHP では主なメモリ管理方法は参照カウントです。ガベージ コレクション機構を導入する目的は、参照カウントにおける循環参照を解消し、これによるメモリ リークを防ぐことです。 ガベージ コレクション メカニズムは、PHP の動的メモリ管理に基づいて存在します。ガベージ コレクション メカニズムを導入するために、PHP5.3 では、以下に示すように、変数ストレージの基本構造がいくつか変更されています。

コードをコピー コードは次のとおりです。


struct _zval_struct {
/* 変数情報 */
zvalue_value value; /* 値 */
zend_uint refcount__gc; /* active type */
zend_uchar is_ref__gc;
};

PHP5.3 より前のバージョンと比較すると、新しいガベージのために参照カウント フィールド refcount と参照フィールド is_ref の両方に __gc が追加されています。収集メカニズム。 PHP のソース コード スタイルでは、多数のマクロが非常に特徴的です。これらのマクロは、インターフェイス層に相当し、ALLOC_ZVAL マクロなど、インターフェイス層の下にある一部の実装を保護します。このマクロは、PHP のメモリ管理割り当て関数 emalloc を直接呼び出してメモリを割り当てていました。 by 変数の型とサイズが決定されます。 ガベージ コレクション メカニズムの導入後、ALLOC_ZVAL マクロは新しいガベージ コレクション ユニット構造を直接採用します。割り当てられるサイズはすべて同じであり、メモリを割り当てた後、zval_gc_info 構造によって占有されるメモリ サイズになります。この構造体は初期化されます。次のコード:

コードをコピー コードは次のとおりです:

/* 次のマクロは、zend_alloc.h のマクロをオーバーライドします。 */
#undef ALLOC_ZVAL
#define ALLOC_ZVAL(z)
do {
(z) = (zval*)emalloc(sizeof(zval_gc_info));
GC_ZVAL_INIT(z); 🎜>} while (0)


zend_gc.h ファイルは、zend.h の 749 行目で参照されています: #include "zend_gc.h" これにより、zend_alloc.h ファイル内の ALLOC_ZVAL などが置き換えられます。 237 行目で参照 マクロ 新しいマクロでは、割り当てられたメモリ サイズと割り当て内容が変更され、ガベージ コレクション メカニズムの内容が以前の純粋なメモリ割り当てに追加されます。すべての内容が zval_gc_info 構造体に含まれます。



コードをコピーします コードは次のとおりです。

typedef struct _zval_gc_info {

zval z;
union {
gc_root_buffer *バッファ済み;
struct _zval_gc_info *next;
} zval_gc_info;
ZVAL コンテナに格納されている変数には、zval 構造体が割り当てられ、zval_gc_info 型のときに zval として使用できるように、zval 変数で割り当てられたメモリの先頭と確実に位置合わせされます。ポインタがキャストされます。 zval フィールドの後に共用体があります: u。 u には、gc_root_buffer 構造体のバッファリングされたフィールドと zval_gc_info 構造体の次のフィールドが含まれます。 これら 2 つのフィールドの 1 つは、ガベージ コレクション メカニズムによってキャッシュされたルート ノードを表し、もう 1 つは、ガベージ コレクション メカニズムによってキャッシュされたノードがルート ノードとして使用されるかリスト ノードとして使用されるかに関係なく、zval_gc_info リスト内の次のノードを表します。ここに反映されます。 ALLOC_ZVAL は、zval を置き換える zval_gc_info を初期化するためにメモリを割り当てた後、GC_ZVAL_INIT を呼び出します。このフィールドは、ガベージ コレクション バッファーに入れられる場合にのみ値を持ちます。それ以外の場合は、常に値が入ります。 NULL にしてください。 PHP のすべての変数は zval 変数の形式で存在するため、ここでは zval_gc_info を使用して zval を置き換えることにより、ガベージ コレクション メカニズムを元のシステムに正常に統合できます。
PHP のガベージ コレクション メカニズムは PHP5.3 ではデフォルトで有効になっていますが、設定ファイルを通じて直接無効に設定できます: zend.enable_gc。 デフォルトでは、php.ini ファイルにこのフィールドはありません。この機能を無効にする必要がある場合は、php.ini に zend.enable_gc=0 または zend.enable_gc=off を追加します。 php.ini 設定 zend.enable_gc を変更することに加えて、gc_enable()/gc_disable() 関数を呼び出してガベージ コレクション メカニズムをオン/オフにすることもできます。 これらの関数を呼び出す効果は、構成項目を変更してガベージ コレクション メカニズムをオンまたはオフにするのと同じです。 これら 2 つの関数に加えて、PHP にはルート バッファがいっぱいでない場合にサイクル リサイクルを強制する gc_collect_cycles() 関数が用意されています。 PHP ソース コードには、ガベージ コレクション メカニズムがオンになっているかどうかに関連する操作とフィールドがいくつかあります。 zend.c ファイルには次のコードがあります:

コードをコピー コードは次のとおりです:


static ZEND_INI_MH( OnUpdateGCEnabled) /* { {{ */
{
OnUpdateBool(entry, new_value, new_value_length, mh_arg1, mh_arg2, mh_arg3, stage TSRMLS_CC);
if (GC_G(gc_enabled)) {
gc_init( TSRMLS_C);
}
return SUCCESS;
}
/* }}} */
ZEND_INI_BEGIN()
ZEND_INI_ENTRY("error_reporting", NULL, ZEND_INI_ALL, OnUpdateErrorReporting)
STD_ZEND_INI_BOOLEAN("zend .enable_gc", "1", ZEND_INI_ALL, OnUpdateGCEnabled, gc_enabled, zend_gc_globals, gc_globals)
#ifdef ZEND_MULTIBYTE
STD_ZEND_INI_BOOLEAN("detect_unicode", "1", OnUpdateBool、detect_unicode、zend_compiler_globals 、compiler_globals)
#endif
ZEND_INI_END()


ガベージ コレクション メカニズムがオンになっている場合、zend.enable_gc の対応する操作関数は ZEND_INI_MH (OnUpdateGCEnabled) です。 (gc_enabled) が true の場合、gc_init 関数が呼び出されます。ガベージ コレクション メカニズムの初期化操作が実行されます。 gc_init 関数は zend/zend_gc.c の 121 行目にあります。この関数はガベージ コレクション メカニズムがオンになっているかどうかを決定します。オンになっている場合、メカニズム全体が初期化されます。つまり、割り当てのために malloc が直接呼び出されます。キャッシュ リスト全体に 10,000 個の gc_root_buffer メモリ スペース。 ここでの 10000 はコード内にハードコーディングされており、マクロ GC_ROOT_BUFFER_MAX_ENTRIES として存在します。この値を変更する必要がある場合は、ソース コードを変更して PHP を再コンパイルする必要があります。 gc_init 関数は、メモリの事前割り当て後に gc_reset 関数を呼び出し、メカニズム全体で使用されるいくつかのグローバル変数をリセットします。たとえば、gc 実行数 (gc_runs) や gc 内のガベージ (収集された) の数の統計を 0 に設定します。 、二重リンクリストの先頭ノードを設定し、前のノードと次のノードがそれ自体を指すようにします。ガベージ コレクション メカニズムで説明したグローバル変数に加えて、他にも一般的に使用される変数があり、その一部については以下で説明します。

コードをコピー コードは次のとおりです。 :


typedef struct _zend_gc_globals {
zend_bool gc_enabled; /* ガベージ コレクション メカニズムをオンにするかどうか*/
zend_bool gc_active; /* 進行中かどうか*/
gc_root_buffer *buf; /* 事前に割り当てられたバッファ配列、デフォルトは 10000 (事前に割り当てられたバッファの配列) */
gc_root_bufferroots; /* リストのルート ノード (サイクルの可能なルートのリスト) */
gc_root_buffer *unused ; /* 未使用のバッファのリスト */
gc_root_buffer *first_unused; /* 最初の未使用のバッファへのポインタ */
gc_root_buffer *last_unused; /* 最後の未使用のバッファへのポインタ*/
zval_gc_info *zval_to_free; /* 解放される zval 変数の一時リスト (解放される zval の一時リスト) */
zval_gc_info *free_list; /* 解放する必要がある一時変数。 release */
zval_gc_info *next_to_free; /* 一時変数、次に解放される変数の位置 */
zend_uint gc_runs; /* gc の実行回数の統計 */
zend_uint が収集されます。 /* gc 内のガベージの数*/
// 省略...
}


この変数が占有しているメモリを unset 操作を使用してクリアすると (参照カウントが 1 つ減るだけかもしれません)、結局のところ、変数名に対応する項目が現在のシンボルのハッシュ テーブルから削除されます。操作が実行され、シンボル テーブルから削除された項目のデストラクターが呼び出されます。一時変数は zval_dtor を呼び出し、一般変数は zval_ptr_dtor を呼び出します。
もちろん、unset 関数は言語構造であるため、PHP の関数セットの中に見つけることはできません。 対応する中間コードは ZEND_UNSET で、関連する実装は Zend/zend_vm_execute.h ファイルにあります。
zval_ptr_dtor は関数ではなく、関数に少し似た単なるマクロです。 Zend/zend_variables.h ファイルでは、このマクロは関数 _zval_ptr_dtor を指します。 Zend/zend_execute_API.c の 424 行目で、関数関連のコードは次のとおりです:

コードをコピー コードは次のとおりです:


ZEND_API void _zval_ptr_dtor( zval **zval_ptr ZEND_FILE_LINE_DC) /* {{{ */
{
#if DEBUG_ZEND>=2
printf("%x (%x) の参照カウントを減らしています: %d->%dn" , *zval_ptr, zval_ptr, Z_REFCOUNT_PP(zval_ptr), Z_REFCOUNT_PP(zval_ptr) - 1);
#endif
Z_DELREF_PP(zval_ptr);
if (Z_REFCOUNT_PP(zval_ptr) = = 0) {
TSRMLS_FETCH ();
if (*zval_ptr != &EG(uninitialized_zval)) {
GC_REMOVE_ZVAL_FROM_BUFFER(*zval_ptr);
zval_ptr(*zval_ptr); zval_ptr);
}
} else {
TSRMLS_FETCH();
if (Z_REFCOUNT_PP(zval_ptr) == 1) {
Z_UNSET_ISREF_PP(zval_ptr)
GC_ZVAL_CHECK_POSSIBLE_RO OT (*zval_ptr);
}
}
/* }}} */


このコードから、次の 2 つの操作が明確にわかります。 :

変数の参照カウントが 1 の場合、つまり、参照カウントが 1 減算されて 0 になった場合、変数を直接クリアします。現在の変数がキャッシュされている場合、変数の参照カウントが 1 より大きい場合、つまり、1 を引いた後の参照カウントが 0 より大きい場合、変数はガベージ リストに置かれます。変更に参照がある場合は、その参照を削除します。

変数をガベージ リストに入れる操作は GC_ZVAL_CHECK_POSSIBLE_ROOT です。これもマクロであり、関数 gc_zval_check_possible_root に対応しますが、この関数は配列とオブジェクトに対してガベージ コレクション操作のみを実行します。配列変数とオブジェクト変数の場合は、gc_zval_possible_root 関数を呼び出します。


コードをコピー コードは次のとおりです:

ZEND_API void gc_zval_possible_root(zval *zv TSRMLS_DC)

{
if ( UNEXPECTED(GC_G(free_list) != NULL &&
GC_ZVAL_ADDRESS(zv) != NULL &&
GC_ZVAL_GET_COLOR(zv) == GC_BLACK) &&
(GC_ZVAL_ADDRESS(zv) < GC_G(buf) ||
GC_ZVAL_ADDRESS(zv) >= GC_G(last_unused))) {
/* 指定された zval は、
* 現在実行中の GC によって削除される予定です */
return ;
}
if (zv->type == IS_OBJECT) {
GC_ZOBJ_CHECK_POSSIBLE_ROOT(zv);
GC_BENCH_INC(zval_possible_root); GC_ZVAL_GET_COLOR(zv ) != GC_PURPLE) {
GC_ZVAL_SET_PURPLE(zv);
if (!GC_ZVAL_ADDRESS(zv)) {
gc_root_buffer *newRoot = GC_G(未使用);
if (newRoot); 🎜>GC_G (unused) = newRoot->prev;
} else if (GC_G(first_unused) != GC_G(last_unused)) {
newRoot = GC_G(first_unused) ;
} else {
if (!GC_G(gc_enabled)) {
GC_ZVAL_SET_BLACK(zv);
}
zv->refcount__gc; );
zv->refcount__gc--;
newRoot = GC_G(未使用) {
}
GC_ZVAL_SET_PURPLE; 🎜>GC_G (未使用) = newRoot->prev;
newRoot->next = GC_G(roots).next;
newRoot->prev = &GC_G(roots); GC_G(roots).next->prev = newRoot;
GC_G(roots).next = newRoot;
newRoot->newRoot; -> u.pz = zv;
GC_BENCH_INC(root_buf_length);
}
}


前述したように、gc_zval_check_possible_root 関数は配列とオブジェクトに対してのみガベージ コレクション操作を実行します。ただし、gc_zval_possible_root 関数では、オブジェクト型の変数に対して GC_ZOBJ_CHECK_POSSIBLE_ROOT マクロが呼び出されます。ガベージ コレクション メカニズムに使用できる他の変数タイプの場合、呼び出しプロセスは次のとおりです。
zval ノード情報がノード バッファーに入れられているかどうかを確認し、ノード バッファーに入れられている場合は、直接戻ります。これによりパフォーマンスが最適化されます。 次に、オブジェクト ノードを処理し、その後の操作を実行せずに直接戻り、ノードが紫色にマークされているかどうかを確認します。これは、ノードがノード バッファーに追加されるだけであることを確認するためです。ノードを一度バッファリングします。

ノードの色を紫としてマークします。これは、ノードがバッファに追加されたことを示します。次回追加する必要はありません。
バッファの場合、新しいノードの場所を見つけます。がいっぱいの場合は、ガベージ コレクション操作を実行します。
バッファが配置されている二重リンクリストに新しいノードを追加します。
gc_zval_possible_root 関数では、バッファーがいっぱいになると、プログラムは gc_collect_cycles 関数を呼び出してガベージ コレクション操作を実行します。 最も重要なステップは です。
行 628 は、公式ドキュメントのアルゴリズムのステップ B であり、アルゴリズムは深さ優先検索を使用して、すべての可能なルートを見つけます。同じ変数コンテナが 2 回「1」減らされないように、1 減分されたものは灰色でマークされています。
629 行目 これはアルゴリズムのステップ C で、アルゴリズムは再び各ルート ノードに対して深さ優先検索を使用し、各変数コンテナの参照カウントをチェックします。 参照カウントが 0 の場合、変数コンテナーは白でマークされます。参照カウントが 0 より大きい場合は、深さ優先検索を使用してこの時点で参照カウントを減分 (つまり、参照カウントを 1 ずつ増やし) した操作を再開し、それらを黒で再マークします。
630 行目 アルゴリズム D の最後のステップでは、アルゴリズムはルート バッファーを走査して変数コンテナー ルート (zval ルート) をそこから削除し、同時に、前のステップ。白でマークされた各変数コンテナはクリアされます。 [gc_collect_cycles() -> gc_collect_roots() -> zval_collect_white() ] では、白でマークされたノードがグローバル変数 zval_to_free リストに追加されることがわかります。このリストは後で使用します。
PHP のガベージ コレクション メカニズムは、実行中にステータスを 4 色でマークします。
GC_WHITE 白はガベージを示します
GC_PURPLE 紫はバッファに入れられたことを示します
GC_GREY 灰色は refcount 操作が 1 つ減算して実行されたことを示します
GC_BLACK 黒はデフォルトの色で、通常
関連タグと オペレーション コードは次のとおりです:

コードをコピー コードは次のとおりです:


#define GC_COLOR 0x03
#define GC_BLACK 0x00
#define GC_WHITE 0x01
#define GC_GREY 0x02
#define GC_PURPLE 0x03
#define GC_ADDRESS(v)
((gc_root_buffer*)((zend_uintptr_t) ) (v)) & ~GC_COLOR))
#define GC_SET_ADDRESS(v, a)
(v) = ((gc_root_buffer*)(((zend_uintptr_t)(v)) & GC_COLOR) | ((zend_uintptr_t)( a))))
# GC_GET_COLOR(v)
(((zend_uintptr_t)(v)) & GC_COLOR)
#define GC_SET_COLOR(v, c)
(v) = ((gc_root_buffer*) )((((zend_uintptr_t)(define v)) & ~GC_COLOR) | (c)))
#define GC_SET_BLACK(v)
(v) = ((gc_root_buffer*)(((zend_uintptr_t)(v) )) & ~GC_COLOR))
#define GC_SET_PURPLE(v)
(v) = ((gc_root_buffer*)((zend_uintptr_t)(v)) | GC_PURPLE))


上のビットはステータスを示します。このメソッドは、メモリ管理など、PHP ソース コードでより頻繁に使用されます。これは、より効率的で経済的なソリューションです。ただし、データベースを設計するときは、このメソッドをフィールドに使用できない場合があります。より直感的で読みやすい方法で実装する必要があります。
声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。