php7的Hashtable实现

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

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

生成迭代器的代码如下:

Hashtable

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

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

间接zval

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

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

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

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

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

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

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

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

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

这代表着,你对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_name就是cv表里的,编译时刻可以确定的zval,它的内存永久有效。而$$var_name是运行时才能确定的变量($$var_name效果等于访问$a),不会存在cv表里。

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

 

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~