关于linux:PHP7内存性能优化的思想精髓

7次阅读

共计 3993 个字符,预计需要花费 10 分钟才能阅读完成。

后面咱们探讨了内存的工作原理,也进行了一些性能相干的测试。那么明天开始咱们来看几个在实践中的利用。首先咱们先从 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 陷阱!

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

正文完
 0