PHP 數組底層是有序映射(HashTable),其記憶體開銷遠超 C 語言原生數組。理解內存模型,有助於在大數據場景下做出正確的數據結構選型。本文通過實測、源碼結構拆解、多維數組對比,量化分析 PHP 數組的真實佔用。
<?php
echo "init mem: " . memory_get_usage() . "\n"; // 58,468 B
$a = array_fill(0, 10000, 1);
echo "10k elements: " . memory_get_usage() . "\n"; // 724,696 B ≈ 66 B/元素
$b = array_fill(0, 10000, 1);
echo "20k elements: " . memory_get_usage() . "\n"; // 1,390,464 B ≈ 66 B/元素
單純整數元素平均佔用 **~66 字節**,而 C 語言 int[10000] 僅需 40 KB(4 字節/元素),PHP 開銷約 **16 倍**。
struct _zval_struct {
zend_value value; // 8 字節 union
zend_refcounted *gc; // 8 字節指針(引用計數/GC)
union { ... } u1; // 類型標誌等
union { ... } u2; // 保留
};
每個變量、數組元素、對象屬性本質都是一個 zval。
struct _zend_array { // 即 HashTable
zend_refcounted_h gc;
union { ... } u;
uint32_t nTableMask;
Bucket *arData; // 指向連續內存塊
uint32_t nNumUsed;
uint32_t nNumOfElements;
uint32_t nTableSize;
uint32_t nInternalPointer;
zend_long nNextFreeElement;
dtor_func_t pDestructor;
};
arData 指向 **Bucket 陣列**,每個 Bucket 佔 **32 字節**(64 位)。| 組件 | 大小 | 說明 |
|---|---|---|
| Bucket | 32 B | 固定 |
| zval (value) | 16 B | 整數直接內嵌 |
| zend_string (key) | ~48 B | 僅字符串鍵;整數鍵無此開銷 |
| HashTable 擴容預留 | ~8 B | 負載因子 0.75,平均分攤 |
| 小計 (整數鍵) | ~56 B | 接近實測 66 B(含 GC、分配器元數據) |
| 小計 (字符串鍵) | ~104 B+ | 含鍵字符串分配 |
<?php
// 一維 10k
$a = array_fill(0, 10000, 1); // ~0.66 MB
// 二維 10k × 10
$b = [];
for ($i=0; $i<10000; $i++) {
$b[$i] = array_fill(0, 10, 1);
} // 理論 ~6.6 MB,實測 ~13 MB (2倍)
// 三維 [10k, 5, 2]
$c = [];
for ($i=0; $i<10000; $i++) {
for ($j=0; $j<5; $j++) {
$c[$i][$j] = [1, 2];
}
} // 實測 ~23 MB (遠超理論 13 MB)
原因:每個子數組都是獨立 HashTable(56 B 固定頭 + Bucket 陣列),大量小數組的元數據開銷主導內存。三維數組 10k × 5 = 5 萬個子數組,光 HashTable 頭部就近 2.8 MB。
$idx = $i * WIDTH + $j,消除子數組開銷。[] 推入子數組。yield 逐行產出,避免全量駐留內存。serialize() / igbinary 存入 Redis/文件,用時反序列化。$empty = []; // ~104 B (zval 16 + HashTable 56 + arData 指針 8 + 分配器對齊)
$arr = [$empty]; // 外層數組再 +56 B 含此元素
結論:PHP 數組適合「中小規模、結構靈活」場景;大規模、高性能、固定結構請改用 SplFixedArray、扁平陣列或專用擴展(如 php-ds、FFI 封裝 C 結構)。