PHP7內存性能優化的思想精髓

前面咱們討論了內存的工做原理,也進行了一些性能相關的測試。 那麼今天開始咱們來看幾個在實踐中的應用。 首先咱們先從PHP開始。

2015年,PHP7的發佈能夠說是在技術圈裏引發了不小的轟動,由於它的執行效率比PHP5直接翻了一倍。PHP7在內存方面,你是否知道做者都進行了哪些優化?幾個核心結構體的改進只是表面上看起來優化的幾個字節那麼簡單?讓咱們從幾個核心的數據結構改進開始看起php

圖片

1PHP7 zval的變化一、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位操做系統下的狀況。該zval_struct結構體中的由四個成員構成,其中zvalue_value稍微複雜一些,是一個聯合體。聯合體中最長的成員是一個指針加一個int,8+4=12字節。可是默認狀況下,會進行內存對齊,故zval_struct會佔用16字節。 那麼。數據結構

_zval_struct總的字節 = value(16)+ refcount__gc(4)+ type(1)+ is_ref__gc(1)= 佔用22字節。app

後再考慮下內存對齊,實際佔用24字節。(若是算的有點暈話,感興趣的同窗能夠寫段簡單的測試代碼,使用sizeof查看一下)ide

二、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結構體裏由3個成員構成,其中zend_value看起來比較複雜,實際上只是一個8字節的聯合體。 u1也是一個聯合體,佔用是4個字節。u2也同樣。這樣zval_struct就實際佔用16個字節。測試

2PHP7 HashTable的變化一、PHP5.3裏的HashTable:
優化

typedef struct _hashtable {
       uint nTableSize;
       uint nTableMask;
       uint nNumOfElements;   //注意這裏:浪費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就是一個大struct, 有點小複雜,咱們拆開了細說,ui

  • uint nTableSize 4字節
  • uint nTableMask 4字節spa

  • uint nNumOfElements 4字節,

  • ulong nNextFreeElement 8字節 注意這前面的4個字節會被浪費掉,由於nNextFreeElement的開始地址須要對齊

  • Bucket *pInternalPointer 8字節

  • Bucket *pListHead 8字節

  • Bucket *pListTail 8字節

  • Bucket **arBuckets 8字節

  • dtor_func_t pDestructor 8字節

  • zend_bool persistent 1字節

  • unsigned char nApplyCoun 1字節

  • zend_bool bApplyProtection 1字節

最終,總字節數 = 4+4+4+4(nNextFreeElement前面這四個字節會留空)+8+8+8+8+8+8+1+1+1 = 67字節。再加上結構體自己要對齊到8的整數倍,因此實際佔用72字節。

二、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 看起來唬人,實際就是個long,佔用8字節
  • union... u 佔用4字節

  • uint32_t 佔用4字節

  • Bucket* 指針佔用8字節

  • uint32_t nNumUsed 佔用4字節

  • uint32_t nNumOfElements 佔用4字節

  • uint32_t nTableSize 佔用4字節

  • uint32_t nInternalPointer 佔用4字節

  • zend_long nNextFreeElement 佔用8字節

  • dtor_func_t pDestructor 佔用8字節

總佔用字節數 = 8+4+4+8+4+4+4+4+8+8 = 56字節,而且正好達到了內存對齊的狀態,沒有額外的浪費。

另外還有PHP源代碼裏常常出鏡的Buckets也從72降低到了32字節,這裏我就不翻源代碼了。

3優化思想精髓噹噹噹,敲黑板,重點來了!咱們看了兩個核心數據結構的結構體變化,這上面的優化都是什麼含義呢? 拿HashTable舉例,貌似從72字節優化到了56字節,這內存節約的也不是特別多嘛,才20%多而已!這中間其實隱藏了個較深層次優化思路

第1、你是否記得咱們前面CPU在向內存要數據的時候是以Cache Line爲單位進行的,而咱們說過Cache Line的大小就是64字節。回過頭來看HashTable,在7.2裏的56字節,只須要CPU向內存進行一次Cache Line大小的burst IO,就夠了。而在5.3裏的72字節,雖然只比Cache Line大了那麼一丟丟,可是對不起,必須得進行兩次burst IO才能夠。 因此,在計算機裏,56字節相對72字節其實是翻倍的性能提高!

第2、CPU的L一、L二、L3的容量是固定的幾十K或者幾十M。假設Cache的都是HashTable,那麼Cache容量不變的條件下,能Cache住的HashTable將會翻倍,緩存命中率提高一大截。要知道L1命中後只須要1ns多一點的耗時,而若是穿透到內存的話可能就須要40多納秒的延時了,整整差了幾十倍。

因此PHP內核的做者大牛深諳CPU與內存的工做原理,表面上看起來只是幾個字節的節約,可是實際上爆發出了巨大的性能提高!

相關文章
相關標籤/搜索