這兩天任務提前完成,可以喘口氣沉淀一下,深入學習學習PHP。其實本來是想了解一下PHP性能優化相關的東西,但被網上的一句“PHP數組內存利用率低,C語言100MB的內存數組,PHP里需要1G”驚到了。PHP真的這么耗內存么?于是借此機會了解了PHP的數據類型實現方式。
先來做個測試:
<?php echo memory_get_usage() , '<br>'; $start = memory_get_usage(); $a = Array(); for ($i=0; $i<1000; $i++) { $a[$i] = $i + $i; } $end = memory_get_usage(); echo memory_get_usage() , '<br>'; echo 'argv:', ($end - $start)/1000 ,'bytes' , '<br>';
所得結果:
353352
437848
argv:84.416bytes
1000個元素的整數數組耗費內存(437848 - 353352)字節,約合82KB,也就是說每個元素所占內存84字節。在C語言中,一個int占位是4字節,整體相差了20倍。
但是網上又說memery_get_usage()返回的結果不全是數組占用,還包括PHP本身的一些結構,因此,換種方式,采用PHP內置函數生成數組試試:
<?php $start = memory_get_usage(); $a = array_fill(0, 10000, 1); $end = memory_get_usage(); //10k elements array; echo 'argv:', ($end - $start )/10000,'byte' , '<br>';
輸出為:
argv:54.5792byte
比剛才略好,但也54字節,確實差了10倍左右。
究其原因,還得從PHP的底層實現說起。PHP是一種弱類型的語言,不分int,double,string之類的,統一一個'$'就能解決所有問題。PHP底層由C語言實現,每個變量都對應一個zval結構,其詳細定義為:
typedef struct _zval_struct zval; struct _zval_struct { /* Variable information */ zvalue_value value; /* The value 1 12字節(32位機是12,64位機需要8+4+4=16) */ zend_uint refcount__gc; /* The number of references to this value (for GC) 4字節 */ zend_uchar type; /* The active type 1字節*/ zend_uchar is_ref__gc; /* Whether this value is a reference (&) 1字節*/ };
PHP使用union結構來存儲變量的值,zval中zvalue_value類型的value變量即為一個union,定義如下:
typedef union _zvalue_value { long lval; /* long value */ double dval; /* double value */ struct { /* string value */ char *val; int len; } str; HashTable *ht; /* hash table value */ zend_object_value obj; /*object value */ } zvalue_value;
union類型占用內存的大小有其最大的成員所占的數據空間決定。在zvalue_value中,str結構體的int占4字節,char指針占4字節,故整個zvalue_value所占內存為8字節。
zval的大小即為8 + 4 + 1 + 1 = 14字節。
注意到zvalue_value中還有一個HashTable是做什么的?zval中,數組、字符串和對象還需要另外的存儲結構,數組的存儲結構即為HashTable。
HashTable定義給出:
typedef struct _hashtable { uint nTableSize; //表長度,并非元素個數 uint nTableMask;//表的掩碼,始終等于nTableSize-1 uint nNumOfElements;//存儲的元素個數 ulong nNextFreeElement;//指向下一個空的元素位置 Bucket *pInternalPointer;//foreach循環時,用來記錄當前遍歷到的元素位置 Bucket *pListHead; Bucket *pListTail; Bucket **arBuckets;//存儲的元素數組 dtor_func_t pDestructor;//析構函數 zend_bool persistent;//是否持久保存。從這可以發現,PHP數組是可以實現持久保存在內存中的,而無需每次請求都重新加載。 unsigned char nApplyCount; zend_bool bApplyProtection; } HashTable;
除了幾個記錄table大小,所含元素數量的屬性變量外,Bucket被多次使用到,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]; /* Must be last element */ } Bucket;
有點像一個鏈表,Bucket就像是一個鏈表節點,有具體的數據和指針,而HashTable就是一個array,保存著一串Bucket元素。PHP中多維數組的實現,不過就是Bucket里面存著另一個HashTable罷了。