PHP數(shù)組內(nèi)存利用率低和弱類型解讀

這兩天任務(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)起意。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鹰服,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子揽咕,更是在濱河造成了極大的恐慌悲酷,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件亲善,死亡現(xiàn)場(chǎng)離奇詭異设易,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蛹头,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門顿肺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人渣蜗,你說我怎么就攤上這事屠尊。” “怎么了袍睡?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵知染,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我斑胜,道長(zhǎng)控淡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任止潘,我火速辦了婚禮掺炭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘凭戴。我一直安慰自己涧狮,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著者冤,像睡著了一般肤视。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上涉枫,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天邢滑,我揣著相機(jī)與錄音,去河邊找鬼愿汰。 笑死困后,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的衬廷。 我是一名探鬼主播摇予,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼吗跋!你這毒婦竟也來了侧戴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤小腊,失蹤者是張志新(化名)和其女友劉穎救鲤,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體秩冈,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡本缠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了入问。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丹锹。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖芬失,靈堂內(nèi)的尸體忽然破棺而出楣黍,到底是詐尸還是另有隱情,我是刑警寧澤棱烂,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布租漂,位于F島的核電站,受9級(jí)特大地震影響颊糜,放射性物質(zhì)發(fā)生泄漏哩治。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一衬鱼、第九天 我趴在偏房一處隱蔽的房頂上張望业筏。 院中可真熱鬧,春花似錦鸟赫、人聲如沸蒜胖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽台谢。三九已至寻狂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間朋沮,已是汗流浹背荆虱。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留朽们,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓诉位,卻偏偏與公主長(zhǎng)得像骑脱,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子苍糠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法叁丧,類相關(guān)的語法,內(nèi)部類的語法岳瞭,繼承相關(guān)的語法拥娄,異常的語法,線程的語...
    子非魚_t_閱讀 31,597評(píng)論 18 399
  • 初冬的暖陽 開在飯后的半晌 久違的明朗 喚醒了多少心底的遠(yuǎn)方瞳筏? 柔暖的日光 恩寵了饑寒的滄桑 跳躍著 明燦燦的金黃...
    MaryMargaret閱讀 352評(píng)論 3 2
  • 【手寫愛情繪本4.0】時(shí)光不僅給我?guī)硇┲匾臇|西稚瘾,關(guān)鍵帶來了你。努力想讓你微笑姚炕,卻總以配角的姿態(tài)不那么引你注意摊欠,...
    主播亞東閱讀 359評(píng)論 0 3
  • 此消彼長(zhǎng) 此消彼長(zhǎng)講得是能量守衡,總量不變柱宦。 當(dāng)動(dòng)能達(dá)到極限時(shí)些椒,勢(shì)能為零,當(dāng)勢(shì)能最大時(shí)掸刊,動(dòng)能達(dá)到極限免糕。這就是機(jī)械能...
    一枚冰兒閱讀 371評(píng)論 0 0