PHP7內(nèi)存性能優(yōu)化的思想精髓

前面我們討論了內(nèi)存的工作原理,也進(jìn)行了一些性能相關(guān)的測試萄唇。那么今天開始我們來看幾個(gè)在實(shí)踐中的應(yīng)用巩检。首先我們先從PHP開始。

2015年,PHP7的發(fā)布可以說是在技術(shù)圈里引起了不小的轟動(dòng)浑槽,因?yàn)樗膱?zhí)行效率比PHP5直接翻了一倍蒋失。PHP7在內(nèi)存方面,你是否知道作者都進(jìn)行了哪些優(yōu)化桐玻?你是否能夠深層次理解到作者優(yōu)化思路的精髓篙挽?

讓我們從幾個(gè)核心的數(shù)據(jù)結(jié)構(gòu)改進(jìn)開始看起。

PHP7 zval變化

1镊靴、php5.3中的zval:

typedef unsigned int zend_object_handle;
typedef struct _zend_object_value {
    zend_object_handle handle;
    zend_object_handlers *handlers;
} zend_object_value;

typedef union _zvalue_value {
    long lval;                  /* long value */
    double dval;                /* double value */
    struct {
        char *val;
        int len;
    } str;
    HashTable *ht;              /* hash table value */
    zend_object_value obj;
} zvalue_value;

struct _zval_struct {
    /* Variable information */
    zvalue_value value;     /* value */
    zend_uint refcount__gc;
    zend_uchar type;    /* active type */
    zend_uchar is_ref__gc;
};

我們這里只討論64位操作系統(tǒng)下的情況铣卡。該_zval_struct結(jié)構(gòu)體中的由四個(gè)成員構(gòu)成,其中zvalue_value稍微復(fù)雜一些偏竟,是一個(gè)聯(lián)合體煮落。聯(lián)合體中最長的成員是一個(gè)指針加一個(gè)int,8+4=12字節(jié)踊谋。但是默認(rèn)情況下蝉仇,會(huì)進(jìn)行內(nèi)存對齊,故_zval_struct會(huì)占用16字節(jié)殖蚕。 那么

_zval_struct總的字節(jié) = value(16)+ refcount__gc(4)+ type(1)+ is_ref__gc(1)= 占用22字節(jié)轿衔。

最后再考慮下內(nèi)存對齊,實(shí)際占用24字節(jié)睦疫。(如果算的有點(diǎn)暈話害驹,感興趣的同學(xué)可以寫段簡單的測試代碼,使用sizeof查看一下)

2笼痛、PHP7.2中的zval

typedef struct _zval_struct     zval;
typedef union _zend_value {
    zend_long         lval;             /* long value */
    double            dval;             /* double value */
    zend_refcounted  *counted;
    zend_string      *str;
    zend_array       *arr;
    zend_object      *obj;
    zend_resource    *res;
    zend_reference   *ref;
    zend_ast_ref     *ast;
    zval             *zv;
    void             *ptr;
    zend_class_entry *ce;
    zend_function    *func;
    struct {
        uint32_t w1;
        uint32_t w2;
    } ww;
} zend_value;
struct _zval_struct {
    zend_value        value;            /* value */
    union {  
        struct {
            ZEND_ENDIAN_LOHI_4(
                zend_uchar    type,         
                zend_uchar    type_flags,
                zend_uchar    const_flags,
                zend_uchar    reserved)     
        } v;
        int type_info;
    } u1;
    union {  ...... } u2;
};

7.2中的_zval_struct結(jié)構(gòu)體里由3個(gè)成員構(gòu)成裙秋,其中zend_value看起來比較復(fù)雜,實(shí)際上只是一個(gè)8字節(jié)的聯(lián)合體缨伊。 u1也是一個(gè)聯(lián)合體,占用是4個(gè)字節(jié)进宝。u2也一樣刻坊。這樣_zval_struct就實(shí)際占用16個(gè)字節(jié)。

PHP7 HashTable變化

1党晋、PHP5.3里的HashTable:

typedef struct _hashtable {
        uint nTableSize;
        uint nTableMask;
        uint nNumOfElements;   //注意這里:浪費(fèi)ing
        ulong nNextFreeElement;
        Bucket *pInternalPointer;       /* Used for element traversal */
        Bucket *pListHead;
        Bucket *pListTail;
        Bucket **arBuckets;
        dtor_func_t pDestructor;
        zend_bool persistent;
        unsigned char nApplyCount;
        zend_bool bApplyProtection;
} HashTable;

再5.3里HashTable就是一個(gè)大struct谭胚, 有點(diǎn)小復(fù)雜,我們拆開了細(xì)說未玻,

  • uint nTableSize 4字節(jié)
  • uint nTableMask 4字節(jié)
  • uint nNumOfElements 4字節(jié)灾而,
  • ulong nNextFreeElement 8字節(jié) 注意這前面的4個(gè)字節(jié)會(huì)被浪費(fèi)掉,因?yàn)閚NextFreeElement的開始地址需要對齊
  • Bucket *pInternalPointer 8字節(jié)
  • Bucket *pListHead 8字節(jié)
  • Bucket *pListTail 8字節(jié)
  • Bucket **arBuckets 8字節(jié)
  • dtor_func_t pDestructor 8字節(jié)
  • zend_bool persistent 1字節(jié)
  • unsigned char nApplyCoun 1字節(jié)
  • zend_bool bApplyProtection 1字節(jié)

最終

總字節(jié)數(shù) = 4+4+4+4(nNextFreeElement前面這四個(gè)字節(jié)會(huì)留空)+8+8+8+8+8+8+1+1+1 = 67字節(jié)扳剿。

再加上結(jié)構(gòu)體本身要對齊到8的整數(shù)倍旁趟,所以實(shí)際占用72字節(jié)。

2庇绽、PHP7.2里的HashTable:

typedef struct _zend_array HashTable;
struct _zend_array {
    zend_refcounted_h gc;
    union {
        struct {
            ZEND_ENDIAN_LOHI_4(
                zend_uchar    flags,
                zend_uchar    nApplyCount,
                zend_uchar    nIteratorsCount,
                zend_uchar    consistency)
        } v;
        uint32_t flags;
    } u;
    uint32_t          nTableMask;
    Bucket           *arData;
    uint32_t          nNumUsed;
    uint32_t          nNumOfElements;
    uint32_t          nTableSize;
    uint32_t          nInternalPointer;
    zend_long         nNextFreeElement;
    dtor_func_t       pDestructor;
};s

在7.2里HashTable

  • zend_refcounted_h gc 看起來唬人锡搜,實(shí)際就是個(gè)long橙困,占用8字節(jié)
  • union... u 占用4字節(jié)
  • uint32_t 占用4字節(jié)
  • Bucket* 指針占用8字節(jié)
  • uint32_t nNumUsed 占用4字節(jié)
  • uint32_t nNumOfElements 占用4字節(jié)
  • uint32_t nTableSize 占用4字節(jié)
  • uint32_t nInternalPointer 占用4字節(jié)
  • zend_long nNextFreeElement 占用8字節(jié)
  • dtor_func_t pDestructor 占用8字節(jié)
總字節(jié)數(shù) = 8+4+4+8+4+4+4+4+8+8 = 56字節(jié)

占用56字節(jié),并且正好達(dá)到了內(nèi)存對齊的狀態(tài)耕餐,沒有額外的浪費(fèi)凡傅。

另外還有PHP源代碼里經(jīng)常出鏡的Buckets也從72下降到了32字節(jié),這里我就不翻源代碼了肠缔。

優(yōu)化思路精髓

我們看了兩個(gè)核心數(shù)據(jù)結(jié)構(gòu)的結(jié)構(gòu)體變化夏跷,這上面的優(yōu)化都是什么含義呢? 拿HashTable舉例明未,貌似從72字節(jié)優(yōu)化到了56字節(jié)槽华,這內(nèi)存節(jié)約的也不是特別多嘛楷力,才20%多而已谎倔!

但這中間其實(shí)隱藏了兩個(gè)較深層次優(yōu)化思路

第一、CPU在向內(nèi)存要數(shù)據(jù)的時(shí)候是以Cache Line為單位進(jìn)行的朴上,而我們說過Cache Line的大小就是64字節(jié)煮纵《遥回過頭來看HashTable,在7.2里的56字節(jié)行疏,只需要CPU向內(nèi)存進(jìn)行一次Cache Line大小的burst IO匆光,就夠了。而在5.3里的72字節(jié)酿联,雖然只比Cache Line大了那么一丟丟终息,但是對不起,必須得進(jìn)行兩次burst IO才可以贞让。 所以周崭,在計(jì)算機(jī)里,72字節(jié)相對56字節(jié)實(shí)際上是翻倍的性能提升T拧续镇!

第二、CPU的L1销部、L2摸航、L3的容量是固定的幾十K或者幾十M。假設(shè)Cache的都是HashTable舅桩,那么Cache容量不變的條件下酱虎,PHP7里能Cache住的HashTable數(shù)量將會(huì)翻倍,緩存命中率提升一大截擂涛。要知道L1命中后只需要1ns多一點(diǎn)的耗時(shí)读串,而如果穿透到內(nèi)存的話可能就需要40多納秒的延時(shí)了,整整差了幾十倍。

所以PHP內(nèi)核的作者大牛深諳CPU與內(nèi)存的工作原理爹土,表面上看起來只是幾個(gè)字節(jié)的節(jié)約甥雕,但是實(shí)際上爆發(fā)出了巨大的性能提升!胀茵!

歡迎搜索微信公眾號:開發(fā)內(nèi)功修煉

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末社露,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子琼娘,更是在濱河造成了極大的恐慌峭弟,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件脱拼,死亡現(xiàn)場離奇詭異瞒瘸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)熄浓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門情臭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人赌蔑,你說我怎么就攤上這事俯在。” “怎么了娃惯?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵跷乐,是天一觀的道長。 經(jīng)常有香客問我趾浅,道長愕提,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任皿哨,我火速辦了婚禮浅侨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘证膨。我一直安慰自己仗颈,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布椎例。 她就那樣靜靜地躺著,像睡著了一般请祖。 火紅的嫁衣襯著肌膚如雪订歪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天肆捕,我揣著相機(jī)與錄音刷晋,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛眼虱,可吹牛的內(nèi)容都是我干的喻奥。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼捏悬,長吁一口氣:“原來是場噩夢啊……” “哼撞蚕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起过牙,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤甥厦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后寇钉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刀疙,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年扫倡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谦秧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,902評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡撵溃,死狀恐怖疚鲤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情征懈,我是刑警寧澤石咬,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站卖哎,受9級特大地震影響鬼悠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜亏娜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一焕窝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧维贺,春花似錦它掂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至垃沦,卻和暖如春客给,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背肢簿。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工靶剑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蜻拨,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓桩引,卻偏偏與公主長得像缎讼,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子坑匠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評論 2 354

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