PHP 5 におけるガベージコレクションアルゴリズムの進化についての簡単な説明

PHP 5 におけるガベージコレクションアルゴリズムの進化についての簡単な説明

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」ファイルにあります。以下は、関連するコードの抜粋です。

  1. typedef ユニオン _zvalue_value {
  2. long lval; /* long 値 */  
  3. double dval; /* 倍精度値 */  
  4. 構造体{
  5. char *val;
  6. 長さ;
  7. } 文字列;
  8. HashTable *ht; /* ハッシュテーブルの値 */  
  9. zend_object_value オブジェクト;
  10. } zvalue_値;
  11.  
  12. 構造体_zval_struct {
  13.      /* 変数情報 */  
  14. zvalue_value 値;
  15. /* 価値 */  
  16. zend_uint refcount__gc;
  17. zend_uchar 型; /* アクティブ型 */  
  18. zend_uchar is_ref__gc;
  19. };

PHP では、すべての変数の値を表すためにユニオン「_zvalue_value」が使用されます。ここでユニオンが使用される理由は、zval が一度に 1 つのタイプの変数しか表すことができないためです。 _zvalue_value には 5 つのフィールドしかありませんが、PHP には NULL を含めて 8 つのデータ型があります。では、PHP はどのようにして 5 つのフィールドを使用して 8 つの型を表現するのでしょうか。これは、フィールドを再利用することでフィールドの数を減らすという PHP の巧妙な設計です。たとえば、PHP では、ブール型、整数、リソース (リソースの識別子が格納されている限り) はすべて lval フィールドを通じて格納されます。dval は浮動小数点型を格納するために使用されます。str は文字列を格納します。ht は配列を格納します (PHP の配列は実際にはハッシュ テーブルであることに注意してください)。obj はオブジェクト型を格納します。すべてのフィールドが 0 または NULL に設定されている場合、PHP では NULL を意味するため、5 つのフィールドに 8 種類の値が格納されます。

現在の zval の値の型 (値の型は _zvalue_value) は、「_zval_struct」の型によって決まります。 _zval_struct は、C 言語における zval の特定の実装です。各 zval は変数のメモリ オブジェクトを表します。 _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 を通じて取得されます)。

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

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

  1. <?php
  2.  
  3. $a =配列();
  4. $a [] = & $a ;
  5. 設定を解除します( $a );
  6.  
  7. ?>

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

灰色の部分はもう存在しません。 before によって指される zval の参照カウントは 1 (HashTable の最初の要素によって参照される) であるため、この zval は GC によって破棄されず、メモリのこの部分はリークされます。

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

PHP が動的ページ スクリプトにのみ使用される場合、動的ページ スクリプトのライフ サイクルは非常に短く、スクリプトの実行時にすべてのリソースが解放されることが PHP によって保証されるため、このリークはそれほど重要ではない可能性があります。しかし、PHP は現在、単なる動的ページ スクリプト以上のものに発展しています。自動テスト スクリプトやデーモン プロセスなど、ライフサイクルが長いシナリオで PHP を使用する場合、複数のサイクルを経て蓄積されたメモリ リークが深刻な問題となる可能性があります。誇張ではありません。私がかつてインターンシップをした会社では、データ ストレージ サーバーとやり取りするために PHP で書かれたデーモン プロセスを使用していました。

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

PHP5.3 のガベージ コレクション アルゴリズム - 参照カウント システムにおける並行サイクル コレクション

PHP5.3 のガベージ コレクション アルゴリズムは依然として参照カウントに基づいていますが、コレクションの基準として単純なカウントは使用されなくなりました。代わりに、IBM のエンジニアが論文「Concurrent Cycle Collection in Reference Counted Systems」で提案した同期コレクション アルゴリズムが使用されます。

このアルゴリズムは、論文が 29 ページにも及ぶことからもわかるように、かなり複雑なので、このアルゴリズムについて十分に議論するつもりはありません (また、議論する能力もありません)。興味のある方は、上記の論文をお読みください (非常に興味深い論文なので、ぜひお読みください)。

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

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

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

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

1. ルート バッファ内の各ルート zval について、深さ優先のトラバース アルゴリズムに従ってトラバースできるすべての zval をトラバースし、各 zval の参照カウントを 1 減らします。同時に、同じ zval が 1 ずつ複数回減らされることを回避するために (異なるルートが同じ zval にトラバースする可能性があるため)、zval が 1 ずつ減らされるたびに、「減らされた」とマークされます。

2. 再度、各バッファのルート zval を深さ優先でトラバースします。zval の参照カウントが 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。

まずメモリ リーク テストです。以下は、PHP マニュアルの実験コードとテスト結果チャートからの直接引用です。

  1. <?php
  2. クラスFoo
  3. {
  4.     公共  $var = '3.1415962654' ;
  5. }
  6.  
  7. $baseMemory = メモリ使用量を取得します。
  8.  
  9. ( $i = 0; $i <= 100000; $i ++ )の場合
  10. {
  11.      $a =新しいFoo;
  12.      $a- >自己 = $a ;
  13.      ( $i % 500 === 0 )の場合
  14. {
  15.          echo sprintf( '%8d: ' , $i ), memory_get_usage() - $baseMemory , "\n" ;
  16. }
  17. }
  18. ?>

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

さらに、パフォーマンスの比較もあります。

  1. <?php
  2. クラスFoo
  3. {
  4.     公共  $var = '3.1415962654' ;
  5. }
  6.  
  7. ( $i = 0; $i <= 1000000; $i ++ )の場合
  8. {
  9.      $a =新しいFoo;
  10.      $a- >自己 = $a ;
  11. }
  12.  
  13. memory_get_peak_usage()をエコーし​​ます"\n" ;
  14. ?>

このスクリプトは、比較するのに十分な遅延時間を確保するために 1,000,000 サイクルを実行します。

次に、CLI を使用して、メモリのリサイクルをオン/オフにしてこのスクリプトを実行します。

  1. 時間 php -dzend.enable_gc=0 -dmemory_limit=-1 -n example2.php
  2. そして 
  3. 時間 php -dzend.enable_gc=1 -dmemory_limit=-1 -n example2.php

私のマシンでは、実行時間はそれぞれ 6.4 秒と 7.2 秒でした。PHP5.3 のガベージ コレクション メカニズムが遅いことがわかりますが、影響は大きくありません。

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

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

オリジナルリンク: http://www.cnblogs.com/leoo2sk/archive/2011/02/27/php-gc.html

【編集者のおすすめ】

  1. 25 の優れた PHP ゲーム プログラミング スクリプト コード共有
  2. PHP エンタープライズ アプリケーション向けの一般的なキャッシュ技術の詳細な解説
  3. Web 開発における PHP と Java の比較
  4. あなたがまだ PHP 初心者であることを示す 40 の兆候
  5. PHPプログラミングのベストプラクティス

<<:  面接中にアルゴリズムの質問を解く際にプログラマーが知っておくべきこと

>>:  JavaScript におけるいくつかの一般的なソートアルゴリズムの共有

ブログ    
ブログ    
ブログ    

推薦する

すべての携帯電話にAIが搭載されているのに、なぜそれを軽蔑するのですか?

携帯電話の発表会を見れば、AI機能の追加が目に入ります。しかし、多くのユーザーはこれをやや否定的に捉...

機械学習のパフォーマンスを最適化するために必要な 6 つの指標

実行している機械学習の種類に応じて、モデルのパフォーマンスを測定するために使用できるメトリックは多数...

繊毛もチップにできる!コーネル大学の中国人博士課程学生の初の論文がネイチャーの表紙に掲載

チップを作る上で最も重要な部分は何ですか? より高度な製造プロセスを使用してトランジスタ密度と計算能...

【文字列処理アルゴリズム】文字列包含アルゴリズムの設計とCコード実装

1. 要件の説明長い文字列と短い文字列が与えられた場合、短い文字列のすべての文字が長い文字列に含まれ...

コンサルタントは AI に置き換えられるでしょうか?主流のコンサルティング会社:心配するよりも受け入れる

多くの企業は、事業運営において専門的なアドバイスを得るためにコンサルタントに依存しており、コンサルテ...

1秒で元の写真に戻る: Adob​​e Photoshop のリバース ツールは、編集した場所を認識して修正するのに役立ちます

この記事はAI新メディアQuantum Bit(公開アカウントID:QbitAI)より許可を得て転載...

ChatGPT App Store は深夜でもオンラインです! 300万GPTがネットワーク上で爆発的に増加し、開発者とOpenAIがその収益を分け合った

先週の発表に続き、OpenAI は本日、GPT ストアの立ち上げを正式に発表しました。写真昨年 11...

...

ベクトル検索エンジン: 大規模な言語モデルの検索と強化された生成のための強力なツール

翻訳者|朱 仙中レビュー | Chonglou導入大規模言語モデル (LLM) が世界を席巻するにつ...

TimePillars: 200メートルを超える小さなターゲットの検出能力の向上

この記事は、Heart of Autonomous Driving の公開アカウントから許可を得て転...

自動運転車の長所と短所

長年にわたる技術の進歩により、交通はより便利になりました。 IoT アプリケーションなどの自動車技術...

1行のコードで大規模モデルのパフォーマンスが10%向上、開発者は無料でランチを楽しめる

大規模なモデルを微調整するための「無料ランチ」ができました。たった 1 行のコードで、パフォーマンス...

人工知能の本質的な「差別」を排除する方法

[[246531]]情報イラスト。出典:新華網ハリウッド映画「アベンジャーズ3」では、悪役サノスが「...

張三が試験でカンニングをしたい場合、どのような暗号化アルゴリズムを使用すればよいでしょうか?先生にバレないように?

「平常時に努力しなければ、試験では友達に頼らざるを得なくなる」ということわざがある。試験が近づくに...

...