PHP 在處理大型陣列時常面臨記憶體效能瓶頸。bkJia 網站曾因調用過大的陣列導致記憶體溢出,使 Apache 進程頻繁癱瘓。雖然大量陣列運算在一定程度上反映程式設計可能存在問題,但準確估算陣列記憶體佔用對效能調優至關重要。
透過 memory_get_usage() 監測記憶體變化:
echo memory_get_usage() . " ";
$a = array();
for ($i = 0; $i < 1000; $i++) {
$a[$i] = $i + $i;
}
echo memory_get_usage() . " ";
for ($i = 1000; $i < 2000; $i++) {
$a[$i] = $i + $i;
}
echo memory_get_usage() . " ";
輸出結果:
58176 162956 267088
可推算 1000 個整數元素約佔用 100KB 記憶體,平均每元素 100 字節。相比之下,純 C 語言中整型陣列僅需 4KB。注意 memory_get_usage() 包含 PHP 運行時本身的結構開銷,使用內建函數生成的陣列更接近真實佔用:
echo "init mem: " . memory_get_usage() . " "; $a = array_fill(0, 10000, 1); echo "10k elements: " . memory_get_usage() . ", system: " . memory_get_usage(true) . " "; $b = array_fill(0, 10000, 1); echo "10k elements: " . memory_get_usage() . ", system: " . memory_get_usage(true) . " ";
結果顯示單個元素約佔用 60 字節左右。
PHP 變數由 zval 結構表示:
struct _zval_struct {
zvalue_value value;
zend_uint refcount__gc;
zend_uchar type;
zend_uchar is_ref__gc;
};
zvalue_value 是一個 union,包含長整數、雙精度浮點數、字串、HashTable 和物件值。典型 zval 需 14 字節。
陣列底層是 HashTable:
typedef struct _hashtable {
uint nTableSize;
uint nTableMask;
uint nNumOfElements;
ulong nNextFreeElement;
Bucket *pInternalPointer;
Bucket *pListHead;
Bucket *pListTail;
Bucket **arBuckets;
dtor_func_t pDestructor;
zend_bool persistent;
unsigned char nApplyCount;
zend_bool bApplyProtection;
} HashTable;
HashTable 結構佔 40 字節。
每個元素存儲在 Bucket 中:
typedef struct bucket {
ulong h;
uint nKeyLength;
void *pData;
void *pDataPtr;
struct bucket *pListNext;
struct bucket *pListLast;
struct bucket *pNext;
struct bucket *pLast;
char arKey[1];
} Bucket;
Bucket 佔 36 字節,鍵名超過 4 字節部分附加在後,元素值通常是另一個 zval。加上 arBuckets 指標陣列,單元素約佔 54 字節,與實測吻合。
空陣列至少佔用 86 字節(zval + HashTable + arBuckets),作為變數在符號表中又需 32 字節,共 118 字節。小型陣列平均成本高,但腳本中通常不會大量存在。
問題在於將陣列作為容器使用時:10k 元素一維陣列約 540KB,10k × 10 二維陣列理論需 6MB,實際卻達兩倍;[10k,5,2] 三維陣列更高達 23MB。小型嵌套陣列的開銷極大,這是效能殺手。
SplFixedArray 替代普通陣列,節省約 30% 記憶體unset() 不再使用的大型陣列釋放記憶體