前面我们讨论了内存的工作原理,也进行了一些性能相关的测试。那么今天开始我们来看几个在实践中的应用。首先我们先从PHP开始。

2015年,PHP7的发布可以说是在技术圈里引起了不小的轰动,因为它的执行效率比PHP5直接翻了一倍。PHP7在内存方面,你是否知道作者都进行了哪些优化?几个核心结构体的改进只是表面上看起来优化的几个字节那么简单?让我们从几个核心的数据结构改进开始看起

1PHP7 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个字节。

2PHP7 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字节,并且正好达到了内存对齐的状态,没有额外的浪费。

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

3优化思想精髓当当当,敲黑板,重点来了!我们看了两个核心数据结构的结构体变化,这上面的优化都是什么含义呢? 拿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才可以。 所以,在计算机里,56字节相对72字节实际上是翻倍的性能提升!

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

所以PHP内核的作者大牛深谙CPU与内存的工作原理,表面上看起来只是几个字节的节约,但是实际上爆发出了巨大的性能提升!


©著作权归作者所有:来自51CTO博客作者mb5fd86a050ef28的原创作品,如需转载,请注明出处,否则将追究法律责任

更多相关文章

  1. 只有1个字节的文件实际占用多少磁盘空间
  2. 新建一个空文件占用多少磁盘空间?
  3. 带你深入理解内存对齐最底层原理
  4. 如何解决U盘老是被占用不能退出的问题
  5. 1Mbps能做什么?
  6. 字节尿性,康托展开求第K个排列!
  7. 一句话锁定MySQL数据占用元凶
  8. android获取网络图片的用法
  9. Android(安卓)进阶——代码插桩必知必会之初识ASM7字节码操作库

随机推荐

  1. zTree.js 异步加载地区例子
  2. jQuery ajax循环和迭代范围
  3. 制作一个基本的angularjs应用程序对我不
  4. 不能让noUISlider工作
  5. iOS中html传数据给原生
  6. 使用wordpress制作Javascript幻灯片
  7. js基础应用-打字机,震动窗口
  8. JavaScript中当前脚本的URL
  9. javascript ArrayBuffer,它的用途是什么?
  10. javascript: 如何编写适合FireFox的对话