這兩天任務(wù)提前完成荧嵌,可以喘口氣沉淀一下小作,深入學(xué)習(xí)學(xué)習(xí)PHP台汇。其實(shí)本來是想了解一下PHP性能優(yōu)化相關(guān)的東西苛骨,但被網(wǎng)上的一句“PHP數(shù)組內(nèi)存利用率低,C語言100MB的內(nèi)存數(shù)組苟呐,PHP里需要1G”驚到了痒芝。PHP真的這么耗內(nèi)存么?于是借此機(jī)會(huì)了解了PHP的數(shù)據(jù)類型實(shí)現(xiàn)方式牵素。
先來做個(gè)測(cè)試:
<?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>';
所得結(jié)果:
353352
437848
argv:84.416bytes
1000個(gè)元素的整數(shù)數(shù)組耗費(fèi)內(nèi)存(437848 - 353352)字節(jié)严衬,約合82KB,也就是說每個(gè)元素所占內(nèi)存84字節(jié)笆呆。在C語言中请琳,一個(gè)int占位是4字節(jié),整體相差了20倍赠幕。
但是網(wǎng)上又說memery_get_usage()返回的結(jié)果不全是數(shù)組占用俄精,還包括PHP本身的一些結(jié)構(gòu),因此劣坊,換種方式嘀倒,采用PHP內(nèi)置函數(shù)生成數(shù)組試試:
<?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字節(jié)局冰,確實(shí)差了10倍左右测蘑。
究其原因,還得從PHP的底層實(shí)現(xiàn)說起康二。PHP是一種弱類型的語言碳胳,不分int,double沫勿,string之類的挨约,統(tǒng)一一個(gè)'$'就能解決所有問題。PHP底層由C語言實(shí)現(xiàn)产雹,每個(gè)變量都對(duì)應(yīng)一個(gè)zval結(jié)構(gòu)诫惭,其詳細(xì)定義為:
typedef struct _zval_struct zval;
struct _zval_struct {
/* Variable information */
zvalue_value value; /* The value 1 12字節(jié)(32位機(jī)是12,64位機(jī)需要8+4+4=16) */
zend_uint refcount__gc; /* The number of references to this value (for GC) 4字節(jié) */
zend_uchar type; /* The active type 1字節(jié)*/
zend_uchar is_ref__gc; /* Whether this value is a reference (&) 1字節(jié)*/
};
PHP使用union結(jié)構(gòu)來存儲(chǔ)變量的值蔓挖,zval中zvalue_value類型的value變量即為一個(gè)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類型占用內(nèi)存的大小有其最大的成員所占的數(shù)據(jù)空間決定。在zvalue_value中,str結(jié)構(gòu)體的int占4字節(jié)怨绣,char指針占4字節(jié)角溃,故整個(gè)zvalue_value所占內(nèi)存為8字節(jié)。
zval的大小即為8 + 4 + 1 + 1 = 14字節(jié)篮撑。
注意到zvalue_value中還有一個(gè)HashTable是做什么的减细?zval中,數(shù)組赢笨、字符串和對(duì)象還需要另外的存儲(chǔ)結(jié)構(gòu)未蝌,數(shù)組的存儲(chǔ)結(jié)構(gòu)即為HashTable。
HashTable定義給出:
typedef struct _hashtable {
uint nTableSize; //表長(zhǎng)度质欲,并非元素個(gè)數(shù)
uint nTableMask;//表的掩碼树埠,始終等于nTableSize-1
uint nNumOfElements;//存儲(chǔ)的元素個(gè)數(shù)
ulong nNextFreeElement;//指向下一個(gè)空的元素位置
Bucket *pInternalPointer;//foreach循環(huán)時(shí),用來記錄當(dāng)前遍歷到的元素位置
Bucket *pListHead;
Bucket *pListTail;
Bucket **arBuckets;//存儲(chǔ)的元素?cái)?shù)組
dtor_func_t pDestructor;//析構(gòu)函數(shù)
zend_bool persistent;//是否持久保存嘶伟。從這可以發(fā)現(xiàn)怎憋,PHP數(shù)組是可以實(shí)現(xiàn)持久保存在內(nèi)存中的,而無需每次請(qǐng)求都重新加載九昧。
unsigned char nApplyCount;
zend_bool bApplyProtection;
} HashTable;
除了幾個(gè)記錄table大小绊袋,所含元素?cái)?shù)量的屬性變量外,Bucket被多次使用到铸鹰,Bucket是如何定義的:
typedef struct bucket {
ulong h; //數(shù)組索引
uint nKeyLength; //字符串索引的長(zhǎng)度
void *pData; //實(shí)際數(shù)據(jù)的存儲(chǔ)地址
void *pDataPtr; //引入的數(shù)據(jù)存儲(chǔ)地址
struct bucket *pListNext;
struct bucket *pListLast;
struct bucket *pNext; //雙向鏈表的下一個(gè)元素的地址
struct bucket *pLast;//雙向鏈表的下一個(gè)元素地址
char arKey[1]; /* Must be last element */
} Bucket;
有點(diǎn)像一個(gè)鏈表癌别,Bucket就像是一個(gè)鏈表節(jié)點(diǎn),有具體的數(shù)據(jù)和指針蹋笼,而HashTable就是一個(gè)array展姐,保存著一串Bucket元素。PHP中多維數(shù)組的實(shí)現(xiàn)剖毯,不過就是Bucket里面存著另一個(gè)HashTable罷了圾笨。
算一算HashTable需要占用39個(gè)字節(jié),Bucket需要33個(gè)字節(jié)逊谋。一個(gè)空的數(shù)組就需要占用14 + 39 + 33 = 86個(gè)字節(jié)擂达。Bucket 結(jié)構(gòu)需要 33 個(gè)字節(jié),鍵長(zhǎng)超過四個(gè)字節(jié)的部分附加在 Bucket 后面胶滋,而元素值很可能是一個(gè) zval 結(jié)構(gòu)板鬓,另外每個(gè)數(shù)組會(huì)分配一個(gè)由 arBuckets 指向的 Bucket 指針數(shù)組, 雖然不能說每增加一個(gè)元素就需要一個(gè)指針究恤,但是實(shí)際情況可能更糟俭令。這么算來一個(gè)數(shù)組元素就會(huì)占用 54 個(gè)字節(jié),與上面的估算幾乎一樣部宿。
從空間的角度來看抄腔,小型數(shù)組平均代價(jià)較大,當(dāng)然一個(gè)腳本中不會(huì)充斥數(shù)量很大的小型數(shù)組,可以以較小的空間代價(jià)來獲取編程上的快捷妓柜。但如果將數(shù)組當(dāng)作容器來使用就是另一番景象了,實(shí)際應(yīng)用經(jīng)常會(huì)遇到多維數(shù)組涯穷,而且元素居多棍掐。比如10k個(gè)元素的一維數(shù)組大概消耗540k內(nèi)存,而10k x 10 的二維數(shù)組理論上只需要 6M 左右的空間拷况,但是按照 memory_get_usage 的結(jié)果則兩倍于此作煌,[10k,5,2]的三維數(shù)組居然消耗了23M,小型數(shù)組確實(shí)是劃不來的赚瘦。
PHP數(shù)組內(nèi)存利用率低的原因粟誓,講到這里,接下來的文章將解讀PHP數(shù)組操作的具體實(shí)現(xiàn)起意。