1. 基本知識
この章では、Zend エンジンのいくつかの内部メカニズムを簡単に紹介します。この知識は拡張機能と密接に関連しており、より効率的な PHP コードを作成するのにも役立ちます。
1.1 PHP変数の保存
1.1.1 zval構造体
Zendはzval構造体を使用してPHP変数の値を保存します。 構造体は次のとおりです:
この表によると、興味深い点が 2 つあります。まず、PHP の配列は実際には HashTable であり、これが PHP が連想配列をサポートできる理由を説明しています。 value、通常はポインタ、内部配列のインデックス、またはその他のものを格納します。作成者自身だけが知っているものはハンドルと見なすことができます
参照カウントはガベージコレクションで広く使用されています。 Zend は、一般的な参照カウントを実装しています。複数の PHP 変数は、参照カウント メカニズムを通じて同じ zval を共有できます。zval の残りの 2 つのメンバー is_ref と refcount は、この共有をサポートするために使用されます。
明らかに、refcount はカウントに使用され、参照が増加または減少すると、この値もそれに応じて増加または減少し、ゼロに減少すると、Zend は zval を再利用します。
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 を実行します。
zval に対して実行されるすべての代入操作が参照または非参照である場合、1 つの is_ref で十分に対応できます。ただし、世界は常にそれほど美しいとは限りません。参照代入と非参照代入を混在させる場合、PHP はユーザーにそのような制限を課すことができません。
このコードの最初の 3 つの文は、is_ref=1、refcount の zval を指します。 =3; 4 番目の文は非参照代入です。通常は参照数を増やすだけで済みます。しかし、単に参照数を増やすだけの Zend の解決策は明らかに間違っています。 d の zval のコピー。
PHP関数のパラメータの受け渡しは、変数の代入と同じであり、参照の受け渡しは参照の代入と同等であり、また、 zval状態切り替えの実行。これについては後述します。
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 またはオペレーティング システムのルーチンを直接使用することもできます。
http://www.bkjia.com/PHPjc/324258.htmlwww.bkjia.comtruehttp://www.bkjia.com/PHPjc/324258.html技術記事 1. 基本知識 この章では、Zend エンジンのいくつかの内部メカニズムを簡単に紹介します。この知識は拡張機能と密接に関連しており、より効率的な PHP コードを作成するのにも役立ちます。 1.1 PHP は...