PHP 7 的發布帶來了巨大的性能提升,其核心在於 zval(Zend Value)結構的徹底重構。本文將對比 PHP 5 與 PHP 7 的 zval 實現差異,深入剖析「不再單獨堆分配、不再自存引用計數」的設計哲學,並詳細解讀全新的引用機制實現。
typedef struct _zval_struct {
zvalue_value value; // 聯合體:存儲各類型值
zend_uint refcount__gc; // 引用計數
zend_uchar type; // 類型標識
zend_uchar is_ref__gc; // 是否 PHP 引用(&)
} zval;
emalloc 一塊 48 字節內存(64 位系統)struct _zval_struct {
zend_value value; // 8 字節值聯合體
union {
struct {
zend_uchar type; // 活躍類型
zend_uchar type_flags; // 類型標志(可計數/可回收/可複製等)
zend_uchar const_flags; // 常量標志
zend_uchar reserved; // 保留
} v;
uint32_t type_info; // 綜合類型信息
} u1;
union { // 輔助信息(按需使用)
uint32_t var_flags;
uint32_t next; // 哈希碰撞鏈
uint32_t cache_slot; // 字面量緩存槽
uint32_t lineno; // 行號(AST節點)
uint32_t num_args; // 參數數量
uint32_t fe_pos; // foreach 位置
uint32_t fe_iter_idx; // foreach 迭代器索引
} u2;
};
zend_value 僅 8 字節,直接存整數/浮點數,複雜類型存指針zend_refcounted 頭部type_flags 標識 IS_TYPE_REFCOUNTED、IS_TYPE_COLLECTABLE、IS_TYPE_COPYABLE 等屬性| 類型 | 可計數 | 可回收 | 可複製 | 不可變 |
|---|---|---|---|---|
| 簡單類型 | ||||
| 字符串 | ✓ | ✓ | ||
| 駐留字符串 | ||||
| 數組 | ✓ | ✓ | ✓ | |
| 不可變數組 | ✓ | |||
| 對象 | ✓ | ✓ | ||
| 資源 | ✓ | |||
| Reference | ✓ |
$a = 42; // zval_1(IS_LONG, 42) $b = $a; // zval_2(IS_LONG, 42) 直接複製值,無共享 $a += 1; // zval_1 變為 43,zval_2 仍為 42
$a = []; // zval_1 --> zend_array_1(refcount=1)
$b = $a; // zval_2 --> zend_array_1(refcount=2)
$a[] = 1; // 分離:zval_1 --> zend_array_2(refcount=1, [1])
// zval_2 --> zend_array_1(refcount=1, [])
#define IS_UNDEF 0 // unset 後的槽位 #define IS_NULL 1 #define IS_FALSE 2 // 拆分布爾類型,優化類型檢查 #define IS_TRUE 3 #define IS_LONG 4 // zend_long,修復 Windows 64 位 long 只有 32 位的問題 #define IS_DOUBLE 5 #define IS_STRING 6 #define IS_ARRAY 7 #define IS_OBJECT 8 #define IS_RESOURCE 9 #define IS_REFERENCE 10 // 全新引用類型 #define IS_CONSTANT 11 #define IS_CONSTANT_AST 12 #define IS_INDIRECT 15 // 間接 zval #define IS_PTR 17 // 內部指針
$a = []; // refcount=1, is_ref=0 $b = $a; // refcount=2, is_ref=0 $c =& $b; // 必須複製 zval:$a/$b 共用 zval_1(is_ref=0), $c/$d 共用 zval_2(is_ref=1) $d[] = 1; // 僅修改 zval_2,$a/$b 不受影響但已分離
後果:引用與非引用變量無法共享同一數組,count($ref) 會觸發大數組完整複製。
struct _zend_reference {
zend_refcounted gc;
zval val; // 內嵌真實值
};
$a = []; // --> zend_array_1(rc=1) $b =& $a; // --> zend_reference_1(rc=2) --> zend_array_1(rc=1) $b[] = 1; // 直接修改共享數組,無需分離 // 混合場景 $a = []; // --> zend_array_1(rc=1) $b = $a; // --> zend_array_1(rc=2) $c = $b; // --> zend_array_1(rc=3) $d =& $c; // --> zend_reference_1(rc=2) --> zend_array_1(rc=3) 完全共享! $d[] = 1; // 此時才分離數組:$a/$b 指向舊數組,$c/$d 指向新數組
count($ref) 不再觸發複製zend_reference 分配開銷,但整體性能大幅提升PHP 7 的 zval 重構是整個性能革命的基石:值內聯、計數下沉、類型精細、訪問扁平。這不僅大幅降低內存佔用(簡單類型從 48 字節降至 16 字節),更消除了 PHP 5 時代固有的架構缺陷。全新的 IS_REFERENCE 機制徹底解決了引用與非引用共存難題,為後續的 JIT 編譯等高級優化奠定了堅實基礎。