PHP7与PHP5的HashTable差异分析

承接上一篇分享,这一次着重对比下PHP7与PHP5的差异。

先看看php7各个元素的结构:

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_3(
    			zend_uchar    type,			/* active type */
    			zend_uchar    type_flags,
    			union {
    				uint16_t  call_info;    /* call info for EX(This) */
    				uint16_t  extra;        /* not further specified */
    			} u)
    	} v;
    	uint32_t type_info;
    } u1;
    union {
    	uint32_t     next;                 /* hash collision chain */
    	uint32_t     cache_slot;           /* cache slot (for RECV_INIT) */
    	uint32_t     opline_num;           /* opline number (for FAST_CALL) */
    	uint32_t     lineno;               /* line number (for ast nodes) */
    	uint32_t     num_args;             /* arguments number for EX(This) */
    	uint32_t     fe_pos;               /* foreach position */
    	uint32_t     fe_iter_idx;          /* foreach iterator index */
    	uint32_t     access_flags;         /* class constant access flags */
    	uint32_t     property_guard;       /* single property guard */
    	uint32_t     constant_flags;       /* constant flags */
    	uint32_t     extra;                /* not further specified */
    } u2;
};

新的zval_struct里面没有的gc字段,u1联合体是用来预测内存分布的,u2里面需要额外关注的是next字段,用来处理数组元素的hash冲突。

typedef struct _Bucket {
    zval              val;
    zend_ulong        h;                /* hash value (or numeric index)   */
    zend_string      *key;              /* string key or NULL for numerics */
} Bucket;

新的bucket相比php5简单的多,去掉了三对指针(数组元素的双向链表指针、hash冲突的双向链表指针、指向zval的指针)。 同时zval由原来的指针改为值,考虑c的内存分布,区别在于栈和堆的关系。

根据上述可知,zval不需要单独分配内存,整个bucket的大小为:16(zval) + 8 + 8 = 32字节,所以占用的内存为:

echo 100000 * 32 / 1024 / 1024, ' m';

约为:3.05M。

实际执行demo代码的时候约为4.00M。是因为php7的bucket分配算法跟php5一样,(我们的情况下)会预先分配2^17个bucket,所以总共是2^17 * 32字节大小的bucket,算下来就是4.00M。与php5不同的是,php7会初始化所有的空间,而php5只会初始化一个指针的大小(8字节)。

HashTable

php7的hashtable结构如下:

struct _zend_array {
        zend_refcounted_h gc;
        union {
                struct {
                        ZEND_ENDIAN_LOHI_4(
                                zend_uchar    flags,
                                zend_uchar    _unused,
                                zend_uchar    nIteratorsCount,
                                zend_uchar    _unused2)
                } 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;
};

typedef struct _zend_array HashTable;

新版的hashtable里面的arData也是直接内嵌,不需要之前的指针了。同时也去掉了原来双向链表的头尾指针。现在的数组的顺序由arData的key来标识,其他字段大同小异。

参考链接

这个博客翻译了几篇不错的文章,非常值得一看

https://gywbd.github.io/posts/2014/12/php7-new-hashtable-implementation.html

(完)