繼續來討論PHP數組效率問題

PHP在數組處理上有時候會非常低效,尤其是內存占用較多,常常使得httpd進程消耗太多資源。PHP100網站就曾因為調用一些內容,數組過大,導致內存據增溢出,apache經常癱瘓。雖然通常在PHP中進行大量數組運算從一定程度上反應程序設計上可能存在問題,但是粗略的估計數組占用的內存是很有必要的。

首先感覺一下1000個元素的整數數組占有的內存:

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 個元素的整數數組需要占用 100k 內存,平均每個元素占用 100 個字節。而純 C 中整體隻需要 4k。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) . “
”;
得到:

init mem: 58468
10k elements: 724696, system: 786432
10k elements: 1390464, system: 1572864
從這個結果來看似乎一個數組元素大約隻占用瞭 60 個左右的字節。再看看數組的C結構,PHP 中的數組變量,首先需要一個 zval 結構:

struct _zval_struct { 
    zvalue_value value;
    zend_uint refcount__gc;
    zend_uchar type;
    zend_uchar is_ref__gc;
};
zvalue_value 是一個union:

typedef union _zvalue_value {
    long lval;
    double dval;
    struct {
        char *val;
        int len;
    } str;
    HashTable *ht;
    zend_object_value obj;
} zvalue_value;
通常 zval 結構需要 8+6=14 個字節,PHP中每個變量都有對應的 zval,但是數組,字符串和對象還需要另外的存儲結構,而數組則是一個 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 個字節,鍵長超過四個字節的部分附加在 Bucket 後面,而元素值很可能是一個 zval 結構,另外每個數組會分配一個由 arBuckets 指向的 Bucket 指針數組, 雖然不能說每增加一個元素就需要一個指針,但是實際情況可能更糟。這麼算來一個數組元素就會占用 54 個字節,與上面的估算相差不遠。

一個空數組至少會占用 14(zval) + 40(HashTable) + 32(arBuckets) = 86 個字節,作為一個變量應該在符號表中有個位置,也是一個數組元素,因此一個空數組變量需要 118 個字節來描述和存儲。從空間的角度來看,小型數組平均代價較大,當然一個腳本中不會充斥數量很大的小型數組,可以以較小的空間代價來獲取編程上的快捷。
但如果將數組當作容器來使用就是另一番景象瞭,實際應用經常會遇到多維數組,而且元素居多。比如10k個元素的一維數組大概消耗540k內存,而10k x 10 的二維數組理論上隻需要 6M 左右的空間,但是按照 memory_get_usage 的結果則兩倍於此,[10k,5,2]的三維數組居然消耗瞭23M,小型數組果然是劃不來的。
 

發佈留言