后面咱们探讨了内存的工作原理,也进行了一些性能相干的测试。那么明天开始咱们来看几个在实践中的利用。首先咱们先从PHP开始。

2015年,PHP7的公布能够说是在技术圈里引起了不小的轰动,因为它的执行效率比PHP5间接翻了一倍。PHP7在内存方面,你是否晓得作者都进行了哪些优化?你是否可能深层次了解到作者优化思路的精华?

让咱们从几个外围的数据结构改良开始看起。

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位操作系统下的状况。该_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字节。

最初再思考下内存对齐,理论占用24字节。(如果算的有点晕话,感兴趣的同学能够写段简略的测试代码,应用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构造体里由3个成员形成,其中zend_value看起来比较复杂,实际上只是一个8字节的联合体。 u1也是一个联合体,占用是4个字节。u2也一样。这样_zval_struct就理论占用16个字节。

PHP7 HashTable变动

1、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, 有点小简单,咱们拆开了细说,

  • uint nTableSize 4字节
  • uint nTableMask 4字节
  • 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字节。

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 看起来唬人,理论就是个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字节

占用56字节,并且正好达到了内存对齐的状态,没有额定的节约。

另外还有PHP源代码里常常出镜的Buckets也从72降落到了32字节,这里我就不翻源代码了。

优化思路精华

咱们看了两个外围数据结构的构造体变动,这下面的优化都是什么含意呢? 拿HashTable举例,貌似从72字节优化到了56字节,这内存节约的也不是特地多嘛,才20%多而已!

但这两头其实暗藏了两个较深层次优化思路

第一、CPU在向内存要数据的时候是以Cache Line为单位进行的,而咱们说过Cache Line的大小就是64字节。回过头来看HashTable,在7.2里的56字节,只须要CPU向内存进行一次Cache Line大小的burst IO,就够了。而在5.3里的72字节,尽管只比Cache Line大了那么一丢丢,然而对不起,必须得进行两次burst IO才能够。 所以,在计算机里,72字节绝对56字节实际上是翻倍的性能晋升!!

第二、CPU的L1、L2、L3的容量是固定的几十K或者几十M。假如Cache的都是HashTable,那么Cache容量不变的条件下,PHP7里能Cache住的HashTable数量将会翻倍,缓存命中率晋升一大截。要晓得L1命中后只须要1ns多一点的耗时,而如果穿透到内存的话可能就须要40多纳秒的延时了,整整差了几十倍。

所以PHP内核的作者大牛深谙CPU与内存的工作原理,外表上看起来只是几个字节的节约,然而实际上暴发出了微小的性能晋升!!



开发内功修炼之内存篇专辑:

  • 1.带你深刻了解内存对齐最底层原理
  • 2.内存随机也比程序拜访慢,带你深刻了解内存IO过程
  • 3.从DDR到DDR4,内存外围频率其实基本上就没太大的提高
  • 4.理论测试内存在程序IO和随机IO时的拜访延时差别
  • 5.揭穿内存厂家“谎话”,实测内存带宽实在体现
  • 6.NUMA架构下的内存拜访提早区别!
  • 7.PHP7内存性能优化的思维精华
  • 8.一次内存性能晋升的我的项目实际
  • 9.挑战Redis单实例内存最大极限,“遭逢”NUMA陷阱!

我的公众号是「开发内功修炼」,在这里我不是单纯介绍技术实践,也不只介绍实践经验。而是把实践与实际联合起来,用实际加深对实践的了解、用实践进步你的技术实际能力。欢送你来关注我的公众号,也请分享给你的好友~~~