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 におけるいくつかの一般的なソートアルゴリズムの共有

ブログ    
ブログ    
ブログ    

推薦する

単眼輝度画像を用いた顔深度マップ推定のための敵対的アーキテクチャによるディープラーニング

本論文では、単眼輝度画像から顔の深度マップを推定する敵対的アーキテクチャを提案する。 画像対画像のア...

2019年の人工知能予測

[[253703]]プライバシーやデータバイアス規制などの問題から、モデルのトレーニングやセルフサー...

1行のコマンドで顔認識を実装する方法を教えます

[[207803]]環境要件ウブントゥ 17.10 Python 2.7.14環境構築1. Ubun...

カメラ、レーダー、地図は不要、二足歩行ロボットは「自分の感覚」で歩く

二足歩行ロボットは高価で複雑、そして壊れやすい。バランスという観点で言えば、二足歩行は四足歩行よりは...

...

GPT-4を使用すると、成績の悪い生徒は成績の良い生徒よりも有利になる

学生が AI を使用して「不正行為」を行うことが許可された場合、成績分布はどのように変化するでしょう...

...

快手八卦についての噂: TensorFlow と PyTorch の並列ボトルネックを打破する分散トレーニング フレームワーク

最近、KuaishouとETH Zurichはオープンソースの分散トレーニングフレームワークBagu...

TikTokの背後にあるAIの仕組み

エンジニアの視点から TikTok 推奨システムのアーキテクチャを探ります。 TikTok は、ユー...

アンビエントコンピューティングが次の大きなトレンドになる理由

アンビエント コンピューティングとは、テクノロジーが環境にシームレスに溶け込み、日常生活に浸透する世...

北京大学はChatGPTを使用して開発チームを構築し、AIが人間の介入なしにソフトウェア開発を自律的に完了します

「一人の能力には限界があるが、チームの力は無限である。」この言葉は、現実世界のソフトウェア開発に鮮や...

アプリランキング操作の水軍が復活:Appleのアルゴリズムを破るために5倍のコストを費やす

[「今回の調整は、主にユーザーのアクティベーション率、アプリの使用頻度、評価など総合的に考慮して行う...

オペレーターの人工知能への道

1年間の開発を経て、人工知能の技術とアプリケーションは、特に通信業界で徐々に爆発的に増加しました。 ...

顔認識を使用してアバターにマスクとゴーグルを自動的に追加する方法

アバターにマスクとゴーグルを追加するプロジェクトアドレス: https://github.com/E...

アルパカファミリーモデルが集合的に進化! Tian Yuandong のチームが作成した、GPT-4 と同等の 32k コンテキスト

オープンソースのアルパカ モデル LLaMA コンテキストは、1 つの簡単な変更だけで GPT-4 ...