1. 基本知識
この章では、Zend エンジンのいくつかの内部メカニズムを簡単に紹介します。この知識は拡張機能と密接に関連しており、より効率的な PHP コードを作成するのにも役立ちます。
1.1 PHP変数の保存
1.1.1 zval構造体
Zendはzval構造体を使用してPHP変数の値を保存します。 構造体は次のとおりです:
コードをコピー コードは次のとおりです:
typedef。 Union _zvalue_value {
long lval; /* double 値 */
struct {
int len; /* ハッシュテーブル値 * /
zend_object_value obj;
struct _zval_struct {
/* 変数情報 */
zvalue_value 値 */
zend_uint refcount; /* アクティブな型 */
zend_uchar is_ref;
};
typedef struct _zval_struct zval ;
Zend は type の値に基づいて value のどのメンバーにアクセスするかを決定します: 利用可能な値は次のとおりです:
IS_NULLN/A
IS_LONG は value に対応します。 .lval
IS_DOUBLEはvalue.dvalに対応
IS_STRINGはvalue.strに対応
IS_ARRAYはvalue.htに対応
IS_OBJECTはvalue.objに対応
IS_BOOLはvalue.lvalに対応
この表によると。まず、PHP 配列は実際には HashTable であるため、PHP が連想配列をサポートできる理由が説明されています。第 2 に、Resource は通常、ポインター、内部配列のインデックス、または
1.1.1 参照カウント
参照カウントはガベージコレクション、メモリプール、文字列などで広く使用されており、Zend は典型的な参照カウントを実装しています。複数の PHP 変数は、参照カウント メカニズムを通じて同じ zval を共有できます。zval の残りの 2 つのメンバー is_ref と refcount は、この共有をサポートするために使用されます。
明らかに、refcount はカウントに使用され、参照が増加または減少すると、この値も増加または減少し、ゼロに減少すると、Zend は zval を再利用します。
is_ref はどうでしょうか?
1.1.2 zval status
PHP には参照と非参照の 2 種類の変数があり、それらはすべて参照カウントを使用して Zend に保存されます。非参照変数の場合、変数は相互に独立している必要があり、1 つの変数を変更する場合、変数を書き込むときに、コピーオンライト メカニズムを使用することでこの競合を解決できます。 Zend は、この変数が指す zval が複数の変数で共有されている場合、refcount が 1 の zval がそこにコピーされ、元の zval の refcount がデクリメントされます。このプロセスは「zval 分離」と呼ばれます。ただし、参照変数の場合は、非参照型の要件とは逆になります。1 つの変数を変更すると、バンドルされたすべての変数が変更される必要があります。
これら 2 つの状況にそれぞれ対処するには、現在の zval の状態を指摘する必要があることがわかります。is_ref は、現在 zval を指しているすべての変数が参照によって割り当てられているかどうかを指摘します。すべての参照またはなし。このとき、別の変数が変更された場合、その zval の is_ref が 0、つまり参照ではないことが判明した場合にのみ、Zend は Copy-On-Write を実行します。
1.1.3 zvalの状態切り替え
zvalに対して実行されるすべての代入操作が参照または非参照である場合、is_refは1つあれば十分に対応できます。ただし、世界は常にそれほど美しいとは限りません。参照代入と非参照代入を混在させる場合、PHP はユーザーにそのような制限を課すことができません。
次の PHP コードを見てください:
プロセス全体は次のとおりです:
このコードの最初の 3 つの文は、a、b、c が zval を指します。その 4 番目の文は、is_ref=1、refcount=3 です。は非参照代入であり、通常は参照カウントを増やすだけで済みます。ただし、ターゲット zval は参照変数であり、d に対して zval の別のコピーを生成するという Zend の解決策は明らかに間違っています。
全体のプロセスは次のとおりです:
1.1.1 パラメータの受け渡し
PHP関数のパラメータの受け渡しは、変数の代入と同じであり、非参照の受け渡しは非参照の代入と同等です。参照の割り当てであり、zval 状態の切り替えを実行させることもできます。これについては後述します。
1.2 HashTable 構造
HashTable は、Zend エンジンで最も重要で広く使用されているデータ構造であり、ほぼすべてのものを保存するために使用されます。
1.1.1 データ構造 HashTable のデータ構造は次のように定義されています:
コードをコピーします
コードは次のとおりです:
typedef structbucket {
ulong h; // ハッシュを格納します
uint nKeyLength; // ユーザーデータのコピーである値を指します
void *pDataPtr; // 構成済みpListNext と pListLast の
structbucket *pListLast; // HashTable 全体の二重リンク リスト
struct Bucket *pNext と
struct Bucket に対応するハッシュを形成するために使用されます。
char arKey[1]; // キー
} Bucket;
uint nTableMask;
uint nNumOfElements; /* 要素の走査に使用されます。 /
バケツ*pListHead;
Bucket *pListTail;
Bucket **arBuckets; // HashTable の初期化時に指定され、Bucket を破棄するときに呼び出されます
unsigned char nApplyCount;
zend_bool bApplyProtection;
# if ZEND_DEBUG
int
} ハッシュテーブル;
一般に、Zend の HashTable はリンク リスト ハッシュであり、以下に示すように、線形トラバーサル用にも最適化されています。
HashTable には、リンク リスト ハッシュと二重リンク リストの 2 つのデータ構造が含まれています。前者は、高速なキーと値のクエリに使用され、後者は、両方のデータ構造に存在します。 。 真ん中。
このデータ構造についてのいくつかの説明:
l なぜ二重リンクリストがリンクリストハッシュで使用されるのですか?
一般的なリンクリストハッシュはキーによって操作するだけでよく、単一リンクリストだけで十分です。ただし、Zend はリンク リストのハッシュから特定のバケットを削除する必要がある場合があります。これは、二重リンク リストを使用すると非常に効率的に実行できます。
l nTableMask は何をするのですか?
この値は、ハッシュ値を arBuckets 配列のインデックスに変換するために使用されます。 HashTable を初期化するとき、Zend はまず arBuckets 配列に nTableSize サイズのメモリを割り当てます。nTableSize はユーザー指定のサイズの最小値 2^n (バイナリで 10*) 以上です。 nTableMask = nTableSize – 1、これはバイナリ 01* です。このとき、h & nTableMask はたまたま [0, nTableSize – 1] に該当し、Zend はそれを arBuckets 配列にアクセスするためのインデックスとして使用します。
l pDataPtr は何をしますか?
通常、ユーザーがキーと値のペアを挿入すると、Zend は値をコピーし、pData が値のコピーを指すようにします。コピー操作では、Zend の内部ルーチン emalloc を呼び出してメモリを割り当てる必要があります。これは非常に時間がかかり、値よりも大きなメモリを消費します (値が小さい場合は、余分なメモリが使用されます)。大きな無駄。 HashTable は主にポインタ値を格納するために使用されることを考慮して、値がポインタと同じくらい小さい場合、Zend はそれを pDataPtr に直接コピーし、pData を pDataPtr にポイントします。これにより emalloc 操作が回避され、キャッシュ ヒット率の向上にも役立ちます。
arKey のサイズが 1 だけなのはなぜですか? キーの管理にポインタを使用しないのはなぜですか?
ArKey はキーを格納する配列ですが、そのサイズは 1 のみで、キーを保持するには十分ではありません。 HashTable の初期化関数には次のコードがあります:
1p = (Bucket *) pemalloc(sizeof(Bucket) - 1 + nKeyLength, ht->persistent);
Zend がスペースを割り当てていることがわかります。それ自体で十分なバケットとキーのメモリの場合、
l 上半分はバケット、下半分はキー、そして arKey は「たまたま」バケットの最後の要素であるため、arKey を使用して次のことを行うことができますキーにアクセスします。この手法はメモリ管理ルーチンで最も一般的であり、メモリが割り当てられると、指定されたサイズよりも大きなメモリが実際に割り当てられ、ブロック サイズや前のブロックなどのメモリに関する情報が保存されます。ポインタ、次ブロック ポインタなど。Baidu の送信プログラムはこのメソッドを使用します。
キーの管理にポインターを使用しないのは、emalloc 操作を 1 回減らし、キャッシュのヒット率を向上させるためです。もう 1 つの必要な理由は、ほとんどの場合、キーが固定されており、キーが長くなるためにバケット全体が再割り当てされないことです。これは、値が変更可能であるため、値が配列として割り当てられない理由も説明します。
1.2.2 PHP Array
HashTable についてはまだ答えのない質問があります、つまり nNextFreeElement は何をするのですか?
一般的なハッシュとは異なり、Zend の HashTable ではキーを無視したり、キーを指定しなくてもハッシュ値を直接指定できます。 (このとき、nKeyLengthは0です)。同時に、HashTable は追加操作もサポートします。ユーザーはハッシュ値を指定する必要さえありません。このとき、Zend は nNextFreeElement をハッシュとして使用し、nNextFreeElement をインクリメントするだけです。
HashTable のこの動作は奇妙に見えます。キーによって値にアクセスできず、ハッシュではないからです。この問題を理解するための鍵は、PHP 配列が HashTable を使用して実装されていることです。連想配列は通常の k-v マッピングを使用して要素を HashTable に追加し、そのキーはユーザーが指定した文字列であり、配列の添字をハッシュ値として直接使用します。キーがあり、配列内で結合要素と非結合要素を混在させる場合、または array_push 操作を使用する場合は、nNextFreeElement を使用する必要があります。
もう一度 value を見てみると、PHP 配列の値は一般構造 zval を直接使用しており、前節の導入によると、この zval* は pDataPtr に直接格納されます。 zval を直接使用するため、配列の要素は任意の PHP タイプにすることができます。
配列トラバーサル操作、つまり foreach や each などは、HashTable の二重リンク リストを通じて実行され、pInternalPointer は現在位置を記録するカーソルとして使用されます。
1.2.3 変数シンボルテーブル
配列に加えて、HashTable は、PHP 関数、変数シンボル、ロードされたモジュール、クラスメンバーなど、他の多くのデータを保存するためにも使用されます。
変数シンボルテーブルは連想配列に相当し、そのキーは変数名であり(長い変数名を使用するのは得策ではないことがわかります)、値はzval*です。
PHP コードはいつでも、symbol_table と active_symbol_table という 2 つの変数シンボル テーブルを参照できます。前者は、グローバル シンボル テーブルと呼ばれるグローバル変数を格納するために使用され、後者は、通常は現在アクティブな変数シンボル テーブルを指すポインターです。グローバルシンボルテーブルです。ただし、PHP 関数 (ここではユーザーが PHP コードを使用して作成した関数を指します) を入力するたびに、Zend は関数に対してローカルな変数シンボル テーブルを作成し、active_symbol_table がローカル シンボル テーブルを指すようにします。 Zend は常に active_symbol_table を使用して変数にアクセスするため、ローカル変数のスコープ制御を実現します。
ただし、グローバルとしてマークされた変数が関数内でローカルにアクセスされると、Zend は特別な処理を実行します。symbol_table に同じ名前の変数が存在しない場合は、同じ名前の変数への参照を active_symbol_table に作成します。最初に作成されます。
1.3 メモリとファイル
プログラムが所有するリソースには通常、メモリとファイルが含まれます。通常のプログラムの場合、これらのリソースはプロセスが終了すると、オペレーティングシステムまたは C ライブラリが明示的に使用していないリソースを自動的にリサイクルします。解放されました。
ただし、PHP プログラムには独自の特性があり、ページが実行されているときは、メモリやファイルなどのリソースにも適用されます。リソースをリサイクルする必要があることを知りません。たとえば、php をモジュールとして Apache にコンパイルし、Apache をプリフォーク モードまたはワーカー モードで実行します。この場合、Apache プロセスまたはスレッドが再利用され、php ページによって割り当てられたメモリは、コアが解放されるまでメモリ内に残ります。
この問題を解決するために、Zend は一連のメモリ割り当て API を提供しています。それらの関数は C の対応する関数と同じです。違いは、これらの関数が Zend 独自のメモリ プールからメモリを割り当て、自動リサイクル ベースを実装できることです。ページ上で。私たちのモジュールでは、ページに割り当てられたメモリは C ルーチンの代わりにこれらの API を使用する必要があります。そうしないと、Zend がページの最後でメモリを解放しようとし、その結果、通常はクラッシュが発生します。
emalloc()
efree()
estrdup()
estrndup()
ecalloc()
erealloc()
さらに、Zend は、C ライブラリと対応するファイル API を置き換えるための VCWD_xxx の形式でマクロのセットも提供します。オペレーティング システムの場合、これらのマクロは PHP の仮想作業ディレクトリをサポートしており、常にモジュール コードで使用する必要があります。マクロの具体的な定義については、PHPソースコード「TSRM/tsrm_virtual_cwd.h」を参照してください。これらすべてのマクロで close 操作が提供されていないことに気づくかもしれません。これは、close のオブジェクトがオープンされたリソースであり、ファイル パスを含まないためです。そのため、同様に C またはオペレーティング システムのルーチン、read/Operations を直接使用できます。 write などでは、C またはオペレーティング システムのルーチンを直接使用することもできます。
上記では、機械設計、製造、オートメーション専攻の紹介、PHP カーネル入門および拡張開発ガイド - 機械設計、製造、オートメーション専攻の紹介を含む基礎知識を紹介します。PHP に興味のある友人の参考になれば幸いです。チュートリアル。