PHP7与PHP5的HashTable差异分析

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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冲突。

1
2
3
4
5
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字节,所以占用的内存为:

1
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结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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

(完)