ご存知のとおり、PHP は他のスクリプト言語と同様に、弱い変数型の言語です。同時に、PHP 自体も C 言語によって実装されます。この記事では主に、PHP 内で弱い変数の型を実装する方法を紹介し、PHP 開発で注意する必要があるいくつかの使用テクニックを分析します。コピーオンライトメカニズムの分析に焦点を当て、PHP の関連トピックを参照します。 この章は、「PHP の詳細な使用スキル」の最初の部分に属します。
弱い変数の実装方法
PHP で弱い変数型を実装する方法を理解する前に、まず次のことを考えてください: C/C++ を通じて弱い変数型の効果を実現するには?
BIT トレーニング クラスでは、この質問に対して基本的に 2 つの答えがありました。
方法 1: C++ の継承メカニズムを使用します。まず基本型を定義します
クラス変数
{
}
次に、Var に基づいて、さまざまなサブタイプ IntVar/FloatVar/StringVar などが派生します。
方法 2: C 言語に基づいた構造体。 1 つのフィールドは型を識別するために使用され、もう 1 つのフィールドはデータを格納するために使用されます。データはさまざまな型である必要があるため、通常はポインター
が必要です。例:
構造体変数 {
int型;
*データを無効にします;
};
両者の考え方自体に大きな違いはなく、基本的にニーズを満たすことができます。 2 番目のアイデアは PHP で採用され、さらなる最適化が行われています。 PHP では、すべての変数は同じ型 zval に対応します。ここで、zval は struct _zval_struct でもあり、具体的には次のように定義されます。
typedef Union _zvalue_value {
long lval; /* 長い値 */
double dval; /* double 値 */
構造体 {
Char *val;
int len;
} str;
HashTable *ht; /* ハッシュ テーブルの値 */
zend_object_value obj;
} zvalue_value;
struct _zval_struct {
/* 可変情報 */
zvalue_value 値; /* 値 */
zend_uint refcount;
zend_uchar タイプ; /* アクティブなタイプ */
zend_uchar is_ref;
};
zval からは、PHP が実際に詳細に多くの最適化努力を行っていることがわかります。1.zend_uchar 型。 uchar を使用してメモリを節約します。
2.zvalue_value value; ユニオンを使用して void * を置き換えると、スペースも節約でき、void * よりもわかりやすくなります。
3. 文字列型の場合、文字列の長さはデフォルトで保持されます。これにより、文字列をバイナリ セーフにすることが簡単になり、文字列の長さを計算するときにスキャンする必要がなくなります。
PHP の弱い変数の実装を観察すると、次のような疑問も抱くでしょう:
1. なぜ int 型がないのですか? 実は PHP にはありますが、デフォルトの int データは long に格納されます。
2. リソースの種類はどのように表現されますか? リソースは実際には PHP 内の数値です。詳細は後ほどご紹介します。
3. refcount と is_ref は何をするのでしょうか? はは、これは第 2 部で紹介する内容です。
参照カウントとコピーオンライト
PHP の構文には、参照代入と非参照代入 (通常の == 代入) の 2 つの代入方法があります。
$a = 1;
$b = $a;//非参照代入
$c = &$a;//参照代入
?>
PHP では参照代入と非参照代入はどのように実装されますか? 共通の理解は次のとおりです。「参照代入は 2 つの変数が同じ Zval に対応することを意味しますが、非参照代入は新しい zval を直接生成し、同時に対応する変数を変更します。」値は直接コピーされます。「つまり、このコードのメモリ構造は次のとおりです。
(この図は、ほとんどの人が PHP のメモリ構造について考えているものですが、これは間違っています)
これは確かにほとんどの状況のニーズを満たすことができますが、特にメモリ管理の観点から見ると、明らかに最適な解決策ではありません。たとえば、次のコードは非常に非効率的です。
$arr = array(...);//非常に大きな PHP 配列を定義します
myfunc($arr);//すべての関数呼び出しは暗黙的な非参照代入です
myfunc($arr);
?>
各関数呼び出しでメモリ ダンプが実行され、大きなメモリのメモリ ダンプは CPU を非常に消費するためです。 C 言語では、解決策の 1 つはポインターを使用し、すべての関数呼び出しでポインターを渡すようにすることです。確かに非常に柔軟で効率的ですが、メンテナンスも難しいです~C言語プログラマにとってポインタは心の痛みとも言えます(もちろんそれは幸いでもありますよ~^_^)。より高度で効率的な方法は、参照カウントを使用することです。PHP では、このような問題を解決するために参照を使用することもできますが、PHP で使用される参照が大量にあるのを見たことがありますか? 明らかに非常に少ないです。
PHP カーネルでは、Zval の実装に参照カウントの概念が採用されています。参照カウントについて話すときは、コピー オン ライト メカニズムについて話さなければなりません。このようにして、前述の refcount と is_ref が機能します。
refcount: 引用の数。 zval が最初に作成されたときは 1 です。参照が追加されるたびに、refcount++ が追加されます。
is_ref: zval が参照状態であるかどうかを示すために使用されます。 zval は初期化すると 0 になり、参照ではないことを意味します。
Zend/Zend.h 内には ZVAL に関するいくつかのマクロ定義があり、以下のマクロ定義に焦点を当てて、参照カウントのルールを明確に分析しています
#define INIT_PZVAL(z)
(z)->refcount = 1;
(z)->is_ref = 0;
#define SEPARATE_ZVAL_IF_NOT_REF(ppzv) //非参照時の変数分離
if (!PZVAL_IS_REF(*ppzv)) {
SEPARATE_ZVAL(ppzv);
}
#define SEPARATE_ZVAL_TO_MAKE_IS_REF(ppzv) //非参照時の変数分離と参照の設定
if (!PZVAL_IS_REF(*ppzv)) {
SEPARATE_ZVAL(ppzv);
(*(ppzv))->is_ref = 1;
}#define SEPARATE_ARG_IF_REF(varptr) //参照中の変数分離
if (PZVAL_IS_REF(varptr)) {
zval *original_var = varptr
ALLOC_ZVAL(varptr)
varptr->value =original_var->value;
varptr->type = Original_var->type;
varptr->is_ref = 0;
varptr->refcount = 1;
zval_copy_ctor(varptr);
} else {
varptr->refcount++;
}
ここでは 2 つの重要な概念について説明します:
1. 非参照時の変数分離。
非参照での変数の分離とは、参照が多数の非参照変数に挿入されるときに PHP 内で実行されるメモリ操作を指します。次の例を見てください:
$a = 1;
$b = $a;
$c = &$b;
最初の 2 つの文が実行された後のメモリ構造は以下のようになります
3 番目の文 $c = &$b; では、「非参照時の変数分離」が実行されます。具体的な手順は次のとおりです。
bを分離し、aに対応するzvalのrefcountを-1に設定します。新しい zval をコピーし、zval の is_ref を 1 に設定します。
C をこの新しい zval と refcount ++
にポイントします。
最終的な効果は以下のようになります:
2.参照中の変数分離。
参照中の変数分離とは、参照変数の束の間で非参照の代入操作を実行することを指します。このとき、コピーメモリ操作が直接実行されます。
次の例を考えてみましょう
$a = 1;
$b = &$a;
$c = $b;
最初の 2 行を実行した後の PHP のメモリ構造は次のようになります:
。
1. PHP 変数の参照カウント機能は配列にも存在します。ただし、キーには効果がないことに注意してください。 (詳細は後の章で分析します。)
2. PHP 変数内のオブジェクトは非常に特殊であり、PHP5 以降では、デフォルトで参照割り当てが使用されます。具体的な実装については、Zend_objects.* シリーズのコードを参照してください。
3. PHP 内部変数を分析するには、組み込みの debug_zval_dump の代わりに xdebug_debug_zval を使用することをお勧めします。なぜなら、PHP の組み込み関数 debug_zval_dump では is_ref を扱えず、参照メソッドを使用して処理するため、結果を見たときに誤解が生じるからです。
活用テクニックのまとめ
この分析に基づいて、多くの結論を引き出すことができます:
1. PHP 開発で参照を使用することはお勧めできません。 PHP はメモリの最適化自体について多くの内部作業を行っているため、参照によっても大きな最適化は行われません。 (ただし、推奨は強制ではないことに注意してください)2. PHP では、strlen は o(1) です。