ホームページ  >  記事  >  バックエンド開発  >  PHP5_PHP チュートリアルのガベージ コレクション アルゴリズム (ガベージ コレクション) の進化に関する簡単な説明

PHP5_PHP チュートリアルのガベージ コレクション アルゴリズム (ガベージ コレクション) の進化に関する簡単な説明

WBOY
WBOYオリジナル
2016-07-21 14:52:29941ブラウズ

前書き: PHP はマネージ言語です。PHP プログラミングでは、プログラマはメモリ リソースの割り当てと解放を手動で処理する必要がありません (C で PHP または Zend 拡張機能を作成する場合を除く)。これは、PHP 自体がガベージ コレクション メカニズムを実装していることを意味します。 (ガベージコレクション)。 PHP 公式 Web サイト (php.net) にアクセスすると、PHP5 の 2 つのブランチ バージョン、PHP5.2 と PHP5.3 が別々に更新されていることがわかります。これは、多くのプロジェクトが依然として PHP 5.2 バージョンを使用しているためです。 5.3 バージョンと 5.2 には完全な互換性がありません。 PHP5.3 では PHP5.2 をベースに多くの改良が加えられており、その中でもガベージコレクションのアルゴリズムは比較的大きな変更となっています。この記事では、PHP5.2 と PHP5.3 のガベージ コレクション メカニズムについてそれぞれ説明し、この進化と改善が PHP を作成するプログラマに与える影響と注意すべき問題について説明します。

PHP変数と関連メモリオブジェクトの内部表現

最終的に、ガベージ コレクションは変数とそれに関連付けられたメモリ オブジェクトの操作です。そのため、PHP のガベージ コレクション メカニズムについて説明する前に、PHP における変数とそのメモリ オブジェクトの内部表現 (C ソース コードでの表現) を簡単に紹介しましょう。 。

公式 PHP ドキュメントでは、PHP の変数をスカラー型と複合型の 2 つのカテゴリに分類しています。スカラー型にはブール型、整数型、浮動小数点型、文字列が含まれ、複合型には配列、オブジェクト、リソースが含まれます。また、どの型にも分割されず、別のカテゴリとなる特殊な NULL もあります。

これらすべての型は、PHP 内で zval と呼ばれる構造体によって統一的に表現されます。PHP ソース コードでは、この構造体の名前は「_zval_struct」です。 zval の具体的な定義は、PHP ソース コードの「Zend/zend.h」ファイルにあります。以下は、関連するコードの抜粋です。

リーリー

ユニオン「_zvalue_value」は、PHP ですべての変数の値を表すために使用されます。ここでユニオンが使用される理由は、zval が一度に 1 つのタイプの変数しか表すことができないためです。 _zvalue_value にはフィールドが 5 つしかないことがわかりますが、PHP には NULL を含めて 8 つのデータ型があります。では、PHP は内部的に 5 つのフィールドを使用して 8 つの型をどのように表すのでしょうか。これは、PHP 設計のより賢い側面の 1 つであり、フィールドを再利用することでフィールドを削減するという目的を達成します。たとえば、PHP 内では、ブール型、整数、リソース (リソースの識別子が保存されている場合) は lval フィールドを介して保存されます。str は文字列を保存します。 PHP では配列は実際にはハッシュ テーブルです)、obj にはオブジェクトの種類が格納されます。すべてのフィールドが 0 または NULL に設定されている場合、PHP では NULL を意味するため、8 種類の値を格納するために 5 つのフィールドが使用されます。

現在のzvalの値の型(値の型は_zvalue_value)は、「_zval_struct」の型によって決まります。 _zval_struct は、C 言語での zval の特定の実装です。各 zval は変数のメモリ オブジェクトを表します。 value と type に加えて、_zval_struct には refcount__gc と is_ref__gc という 2 つのフィールドがあることがわかります。それらのサフィックスから、これら 2 つはガベージ コレクションに関連していると結論付けることができます。そうです、PHP のガベージ コレクションはこれら 2 つのフィールドに完全に依存しています。このうち、refcount__gc は、現在この zval を参照している変数がいくつかあることを示し、is_ref__gc は、現在の zval が参照によって参照されているかどうかを示します。これは、PHP の zval の「Write-On-Copy」メカニズムに関連しています。このトピックはこの記事の焦点では​​ないため、ここでは詳しく説明しません。読者は refcount__gc フィールドの役割だけを覚えておく必要があります。

PHP5.2 のガベージ コレクション アルゴリズム—参照カウント

PHP5.2 で使用されるメモリ再利用アルゴリズムは、有名な参照カウントです。このアルゴリズムの中国語訳は「参照カウント」と呼ばれ、その考え方は非常に直感的で簡潔です。各メモリ オブジェクトにカウンタを割り当て、カウンタを初期化します。メモリ オブジェクトが作成されたときは 1 (つまり、この時点では常にこのオブジェクトを参照する変数が存在します)、新しい変数がこのメモリ オブジェクトを参照するたびに、カウンタは 1 ずつ増加します。オブジェクトが減少すると、カウンタは 1 ずつ減ります。 ガベージの場合、リサイクル メカニズムが動作すると、カウンタが 0 のすべてのメモリ オブジェクトが破棄され、それらによって占有されていたメモリがリサイクルされます。 PHP のメモリ オブジェクトは zval、カウンタは refcount__gc です。

たとえば、次の PHP コードは、PHP5.2 カウンターの動作原理を示しています (カウンター値は xdebug.org を通じて取得されます)。


$val1 = 100; //zval(val1).refcount_gc = 1;
$val2 = $val1; //zval(val1).refcount_gc = 2,zval(val2).refcount_gc = 2 (コピー時書き込みなので、現在 val2 と val1 は一緒に zval を参照します)
$val2 = 200; //zval(val1).refcount_gc = 1,zval(val2).refcount_gc = 1 (val2 はここで新しい zval を作成します)
unset($val1); //zval(val1).refcount_gc = 0 ($val1 によって参照される zval は使用できなくなり、GC によってリサイクルされます)

?>
参照カウントはシンプルで直感的で実装が簡単ですが、メモリ リークを引き起こしやすいという致命的な欠陥があります。多くの友人は、循環参照がある場合、参照カウントによってメモリ リークが発生する可能性があることに気づいたかもしれません。たとえば、次のコード:

このコードは、まず配列 a を作成し、次に a の最初の要素を参照によって指すようにします。このとき、a の zval の refcount は 2 になります。このとき、変数 a は破棄されます。 a が最初に指した zval の は 1 ですが、以下の図に示すように、循環自己参照を形成するため、これを操作することはできません。

PHP5_PHP チュートリアルのガベージ コレクション アルゴリズム (ガベージ コレクション) の進化に関する簡単な説明

灰色の部分は、もう存在しないことを意味します。 a が指す zval の refcount は 1 (その HashTable の最初の要素によって参照される) であるため、この zval は GC によって破壊されず、メモリのこの部分がリークされます。

ここで特に重要なのは、PHP はシンボル テーブル (シンボル テーブル) を介して変数シンボルを格納することです。したがって、配列やオブジェクトなどの各複合型には独自のシンボル テーブルがあります。上記のコードでは、a と a[0] は 2 つのシンボルですが、a はグローバル シンボル テーブルに格納され、a[0] は配列自体のシンボル テーブルに格納されます。ここで、a と a[0] は、同じ zval (もちろん、シンボル a は後で破棄されます)。読者の皆様には、シンボル (Symbol) と zval の関係を区別することに注意していただければ幸いです。

PHP が動的ページ スクリプトのみに使用されている場合、動的ページ スクリプトのライフサイクルは非常に短く、PHP はスクリプトの実行時にすべてのリソースが確実に解放されるため、このリークはそれほど重要ではない可能性があります。ただし、PHP は、もはや動的ページ スクリプトとしてのみ使用されるだけではないところまで発展しています。自動テスト スクリプトやデーモン プロセスなど、ライフ サイクルの長いシナリオで PHP を使用すると、多くのサイクルの後にメモリ リークが蓄積される可能性があります。大変なことになりますよ。これはセンセーショナルではありません。私がかつてインターンをしていた会社では、データ ストレージ サーバーと対話するために PHP で書かれたデーモン プロセスを使用していました。

参照カウントのこの欠陥により、PHP5.3 ではガベージ コレクション アルゴリズムが改善されました。

PHP5.3 のガベージ コレクション アルゴリズム—参照カウント システムの同時サイクル コレクション

PHP5.3 のガベージ コレクション アルゴリズムは依然として参照カウントに基づいていますが、リサイクル基準として単純なカウントは使用されなくなり、代わりに同期リサイクル アルゴリズムが使用されます。このアルゴリズムは、論文

Concurrent Cycle Collection で IBM エンジニアによって提案されました。数えられるシステムで提案された参照

このアルゴリズムは非常に複雑であることは、この論文の 29 ページを見れば誰でもわかると思います。そのため、このアルゴリズムについて詳しく説明するつもりはありません (また、その能力はありません)。興味のある方は、上記の論文を読んでください (強くお勧めします)。 )、この論文は非常に刺激的です)。

ここではこのアルゴリズムの基本的な考え方を大まかに説明することしかできません。

まず、PHP は固定サイズの「ルート バッファ」を割り当てます。このバッファは、固定数の zval を保存するために使用されます。変更する必要がある場合は、ソース コード内の定数を変更する必要があります。 Zend/zend_gc.c GC_ROOT_BUFFER_MAX_ENTRIES を実行して再コンパイルします。

上記のことから、zval に参照がある場合、それはグローバル シンボル テーブル内のシンボルによって参照されるか、複合型を表す他の zval 内のシンボルによって参照されることがわかります。したがって、zval にはいくつかのルートがある可能性があります。ここでは、PHP がこれらの考えられるルートを検出する方法については説明しません。これは非常に複雑な問題です。つまり、PHP には、これらの考えられるルート zval を検出し、ルート バッファーに入れる方法があります。

ルートバッファがいっぱいになると、PHPはガベージコレクションを実行します。リサイクルアルゴリズムは次のとおりです。

1. 各ルートバッファーのルート zval について、深さ優先探索アルゴリズムに従って探索可能なすべての zval を探索し、各 zval の refcount を 1 ずつ減らします。同時に、同じ値が減らないようにするために、 zval は複数回 1 ずつ変更され (異なる可能性があるため、ルートは同じ zval にたどり着くことができます)、zval が 1 ずつデクリメントされるたびに、「デクリメント済み」としてマークされます。

2. 各バッファーのルート zval を深さ優先で再度トラバースします。zval の refcount が 0 でない場合は 1 を追加し、それ以外の場合は 0 のままにします。

3. ルート バッファー内のすべてのルートをクリアし (これらの zval は破棄されるのではなく、バッファーからクリアされることに注意してください)、refcount が 0 のすべての zval を破棄し、メモリを再利用します。

完全に理解していなくても大丈夫です。PHP5.3 のガベージ コレクション アルゴリズムには次のような特徴があることを覚えておいてください。

1. refcount が減少するたびにリサイクル サイクルが開始されるわけではありません。ガベージ コレクションはルート バッファーがいっぱいになった後にのみ開始されます。

2. 循環参照問題を解決できる。

3. メモリ リークは常にしきい値未満に抑えることができます。

PHP5.2とPHP5.3のガベージコレクションアルゴリズムのパフォーマンス比較

現在の制限のため、実験を再設計することはしませんが、PHP マニュアルの実験を直接引用します。2 つのパフォーマンスの比較については、PHP マニュアルの関連する章を参照してください: http://www.php .net /manual/en/features.gc.performance-considerations.php。

1 つ目は、PHP マニュアルの実験コードとテスト結果の図を以下に直接引用します。


$a = 配列();
$a[] = & $a;
unset($a);

?>

クラスフー
{
パブリック $var = '3.1415962654';
}

$baseMemory = メモリ_get_usage();

for ( $i = 0; $i {
$a = 新しい Foo;
$a->self = $a;
If ( $i % 500 === 0 )
{
echo sprintf( '%8d: ', $i )、memory_get_usage() - $baseMemory, "n";
}
}
?>

PHP5_PHP チュートリアルのガベージ コレクション アルゴリズム (ガベージ コレクション) の進化に関する簡単な説明

累積メモリ リークを引き起こす可能性のあるシナリオでは、PHP5.2 では継続的に累積メモリ リークが発生しますが、PHP5.3 ではメモリ リークを常にしきい値 (ルート バッファ サイズに関連する) 以下に制御できることがわかります。

パフォーマンスの比較についても:

クラスフー
{
パブリック $var = '3.1415962654';
}

for ( $i = 0; $i {
$a = 新しい Foo;
$a->self = $a;
}

エコーmemory_get_peak_usage(), "n";
?>

このスクリプトはループを 1,000,000 回実行して比較に十分な遅延時間を作成し、次に CLI を使用してメモリ リサイクルをオンおよびメモリ リサイクルをオフにしてこのスクリプトを実行します。

時間 php -dzend.enable_gc=0 -dmemory_limit=-1 -n example2.php

#そして
時間 php -dzend.enable_gc=1 -dmemory_limit=-1 -n example2.php

私のマシン環境では、実行時間はそれぞれ6.4秒と7.2秒でした。PHP5.3のガベージコレクション機構により遅くなることがわかりますが、影響は大きくありません。

ガベージコレクションアルゴリズムに関連するPHP設定

php.ini の zend.enable_gc を変更することで PHP のガベージ コレクション メカニズムをオンまたはオフにすることができます。また、gc_enable() または gc_disable() を呼び出して PHP のガベージ コレクション メカニズムをオンまたはオフにすることもできます。 PHP5.3 でガベージ コレクション メカニズムがオフになっている場合でも、PHP はルートの可能性をルート バッファーに記録しますが、ルート バッファーがいっぱいになると、PHP は自動的にガベージ コレクションを実行しません。もちろん、手動で gc_collect_cycles を呼び出すこともできます。 ( ) 関数はメモリ再利用を強制します。

この記事は表示-非営利 3.0 ライセンス契約に基づいて公開されており、転載と解釈は歓迎されますが、この記事の署名

張楊 (リンクを含む) は保持しなければならず、商業目的は許可されません。

http://www.bkjia.com/PHPjc/371631.htmlwww.bkjia.comtru​​ehttp://www.bkjia.com/PHPjc/371631.html技術記事まえがき: PHP はマネージド言語です。PHP プログラミングでは、プログラマーはメモリ リソースの割り当てと解放を手動で処理する必要がありません (C を使用して PHP または Zend 拡張機能を作成する場合を除く)。つまり、PHP 自体が...
声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。