php7的hastable,合肥PHP培训php7的Hashtable实现

078bcf6fae1928a4b024ac9e3cacf385.png

今天修复PHP-X项目的一个BUG时,顺便把php7的hashtable实现原理简单过了一下。

代码来源于PHP-X项目里的一个数组迭代器,它里面涉及了如何遍历一个hashtable,以及间接zval的访问:

class ArrayIterator

{

public:

ArrayIterator(Bucket *p)

{

_ptr = p;

_key = _ptr->key;

_val = &_ptr->val;

_index = _ptr->h;

pe = p;

}

ArrayIterator(Bucket *p, Bucket *_pe)

{

_ptr = p;

_key = _ptr->key;

_val = &_ptr->val;

_index = _ptr->h;

pe = _pe;

}

void operator ++(int i)

{

while (++_ptr != pe)

{

_val = &_ptr->val;

if (_val && Z_TYPE_P(_val) ==

IS_INDIRECT)

{

_val = Z_INDIRECT_P(_val);

}

if (UNEXPECTED(Z_TYPE_P(_val) == IS_UNDEF))

{

continue;

}

if (_ptr->key)

{

_key = _ptr->key;

_index = 0;

}

else

{

_index = _ptr->h;

_key = NULL;

}

break;

}

}

bool operator !=(ArrayIterator b)

{

return b.ptr() != _ptr;

}

Variant key()

{

if (_key)

{

return Variant(_key->val, _key->len);

}

else

{

return Variant((long) _index);

}

}

Variant value()

{

return Variant(_val);

}

Bucket *ptr()

{

return _ptr;

}

private:

zval *_val;

zend_string *_key;

Bucket *_ptr;

Bucket *pe;

zend_ulong _index;

};

生成迭代器的代码如下:

ArrayIterator begin()

{

return

ArrayIterator(Z_ARRVAL_P(ptr())->arData, Z_ARRVAL_P(ptr())->arData +

Z_ARRVAL_P(ptr())->nNumUsed);

}

ArrayIterator end()

{

return

ArrayIterator(Z_ARRVAL_P(ptr())->arData + Z_ARRVAL_P(ptr())->nNumUsed);

}

Hashtable

什么是Bucket?为什么关联数组和非关联数组都可以通过顺序访问Bucket数组来实现遍历呢?

我找到一篇很好的博客讲解了PHP7中hashtable的实现,点击阅读。

间接zval

另外值得一提是的”间接zval”,也就是这段代码背后的含义:

if (_val && Z_TYPE_P(_val) ==

IS_INDIRECT)

{

_val = Z_INDIRECT_P(_val);

}

这是PHP7的一个优化措施,宏观的理解起来不是很困难,下面慢慢道来。

透过Z_INDIRECT_P这个宏,我们就能知道背后的大概实现原理,所以我们进入zend源码来分析。

#define Z_INDIRECT(zval) (zval).value.zv

#define Z_INDIRECT_P(zval_p)

Z_INDIRECT(*(zval_p))

这个宏简单的取出zval对象的value属性,我们先看看php7中zval的样子:

struct _zval_struct {

zend_value value; /* value */

union {

struct {

ZEND_ENDIAN_LOHI_4(

zend_uchar type, /* active type */

zend_uchar type_flags,

zend_uchar const_flags,

zend_uchar reserved) /* call info for

EX(This) */

} v;

uint32_t type_info;

} u1;

union {

uint32_t next; /* hash collision chain */

uint32_t cache_slot; /* literal cache slot

*/

uint32_t lineno; /* line number (for ast

odes) */

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 extra; /* not further specified */

} u2;

};

一个zval有固定的类型,上面也是通过宏来获取的,先看一下:

static zend_always_inline zend_uchar

zval_get_type(const zval* pz) {

return pz->u1.v.type;

}

/* we should never set just Z_TYPE, we

should set Z_TYPE_INFO */

#define Z_TYPE(zval)

zval_get_type(&(zval))

#define Z_TYPE_P(zval_p) Z_TYPE(*(zval_p))

可见,通过zval.u1.v.type就可以知道这个zval是什么类型,它存储的值大概是这些:

/* regular data types */

#define IS_UNDEF 0

#define IS_NULL 1

#define IS_FALSE 2

#define IS_TRUE 3

#define IS_LONG 4

#define IS_DOUBLE 5

#define IS_STRING 6

#define IS_ARRAY 7

#define IS_OBJECT 8

#define IS_RESOURCE 9

#define IS_REFERENCE 10

/* constant expressions */

#define IS_CONSTANT 11

#define IS_CONSTANT_AST 12

/* fake types */

#define _IS_BOOL 13

#define IS_CALLABLE 14

#define IS_ITERABLE 19

#define IS_VOID 18

/* internal types */

#define IS_INDIRECT 15

#define IS_PTR 17

#define _IS_ERROR 20

上面那些基础类型都是直接zval,只有IS_INDIRECT表示这是一个”间接zval”,那么何为”间接”呢?

我们回到zval.value这个属性,它的类型是zend_value:

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;

它是一个union,也就是根据zval的类型,决定了zval.value里面使用哪个字段保存具体类型的值(的地址),而具体值的引用计数是保存在具体类型里的(整形,布尔这种不需要引用计数),比如zend_string的定义中有这样一个zend_refcounted_h gc的引用计数的字段:

struct _zend_string {

zend_refcounted_h gc;

zend_ulong h; /* hash value */

size_t len;

char val[1];

};

这代表着,你对zval进行浅拷贝是不会修改引用计数的,必须通过zend api对zval.value内的具体对象进行引用计数操作,这一块我是顺便扯一下,我们还是回到”间接zval”。

间接zval的value中保存的不是zend_string*,也不是zend_arrary*等等,它保存的是zval *zv,也就是记录了另外一个zval对象的地址,这是很奇怪的,因为php7已经把zval设计为栈存储了,为什么zval内又保存了一个zval的指针呢?下面是重点!

这里要说一下PHP7的CV表,其全拼是compiled variable,也就是编译时可以确定的变量。只要你是通过$a,$b这样在代码里定义的变量都会在编译时刻保存在一个全局的table里,你可以理解为zval cv[100000]这样一个大数组里,每一个zval对应编译时确定的变量,也就是cv[0]是$a,cv[1]是$b,这个cv表一旦解析为opcode就固定了,其中的每个zval的内存永久存在,当然你可以删除$a,比如unset($a),这样带来的效果只是cv[0].u1.v.type == IS_UNDEF而已!

那么我们也知道,PHP允许这样玩:

$var_nane = "a";

$$var_name = "b";

那么$var_name就是cv表里的,编译时刻可以确定的zval,它的内存永久有效。而$$var_name是运行时才能确定的变量($$var_name效果等于访问$a),不会存在cv表里。

关注的重点是理解cv表,至于$$var_name这种用法不是重点。重点是,cv表中的zval其生命期伴随PHP脚本执行一直存在,所以优化就是我们完全可以定义一个”间接zval”来指向cv表中的zval,不需要管理引用计数,就是这么回事。

今天就给大家讲这么多吧,php作为开发类的一个语言,现在受到越来越多人的关注,选择合肥PHP培训班,不再孤军奋战,轻轻松松做IT高薪白领合肥达内培训带领有明确目标的学子迈向成功之路!

【免责声明】本文系本网编辑部分转载,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及作品内容、版权和其它问题,请在30日内与管理员联系,我们会予以更改或删除相关文章,以保证您的权益!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值