Press "Enter" to skip to content

深入理解PHP7内核之Reference

问题

上一章说过引用(REFERENCE)在PHP5的时候是一个标志位, 而在PHP7以后我们把它变成了一种新的类型:IS_REFERNCE. 然而引用是一种很常见的应用, 所以这个变化带来了很多的变化, 也给我们在做PHP7开发的时候, 因为有的时候疏忽忘了处理这个类型, 而带来不少的bug.

最简单的情况, 就是在处理各种类型的时候, 从此以后我们要多考虑这种新的类型, 比如在PHP7中, 这样的代码形式就变得很常见了:

try_again:
swtich (Z_TYPE_P(zv)) {
	case IS_TRING:
	break;
	case IS_ARRAY:
	break;
    ...
	case IS_REFERENCE:
	zv = Z_REFVAL_P(zv); //解引用
	goto try_again;
	break;
}

如果大家自己写的扩展, 如果忘了考虑这种新的类型, 那么就会导致问题.

为什么?

那么既然这种新类型会带来这么多问题, 那么当时为什么要用把引用变成一种类型呢? 为什么不还是使用一个标志位呢?
一句话来说, 就是我们不得不这么做. -_#
前面说到, Hashtable直接存储的是zval, 这样在符号表中, 俩个zval如何共用一个数值呢? 对于字符串等复杂类型来说还好, 我们貌似可以在zend_refcounted结构中加入一个标志位来表明是引用来解决, 然而这个也会遇到Change On Write带来的复制, 但是我们知道在PHP7中, 一些类型是直接存储在zval中的, 比如IS_LONG, 但是引用类型是需要引用计数的, 那么对于一个是IS_LONG并且又是IS_REFERNCE的zval该如何表示呢?
为此, 我们创造了这个新的类型:

如图所示, 引用是一种新的类型:zend_reference, 对于IS_REFERNCE类型的zval, zval.value.ref是一个指向zend_reference的指针, 它包含了引用计数和一个zval, 具体的zval的值是存在zval.value.ref->val中的.
所以对于IS_LONG的引用来说, 就用一个类型是IS_REFERNCE的zval, 它指向一个zend_reference, 而这个zend_reference->val中是一个类型为IS_LONG的zval.

Change On Write

PHP采用引用计数来做简单的垃圾回收, 考虑如下的代码:

<?php
1. $val = "laruence";
2. $ref = &$val;
3. $copy = $val;
?>

$ref和$val是指向同一个zval的引用, 在PHP5的时候, 我们是通过一个引用计数为2, 并且引用标志位为1来表示这种情况, 当把$val复制给$copy(line 3)的时候, 我们发现$val是一个计数大于1的引用, 所以要产生Change on write, 也就是分离. 所以我们需要复制这个zval.
而在PHP7中, 情况就变得简单了很多, 首先在引用赋值给$ref(line 2)的时候, 生成一个IS_REFERNCE类型, 然后因为此时有俩个变量引用它所以zend_reference这个结构的引用计数zval.value.ref->gc.refcount为2.
再随后的赋值给$copy(line 3)的时候, 发现$val是一个引用, 于是让$copy指向的是zval.value.ref->val, 也就是字符串值为laruence的zval, 然后把zval的引用计数+1, 也就是zval.value.ref->val.value.str.gc.refcount为2. 并没有产生复制.
从而这就很好的解决了上一章所说的PHP5的那个经典的问题, 比如我们在PHP7下运行上一章的那个问题, 我们得到的结果是:

$ php-7.0/sapi/cli/php /tmp/1.php
Used 0.00021380008539
Used 0.00020173048281

可见确实没有发生复制, 从而不会产生任何的性能问题.

67 Comments

  1. Rhythm
    Rhythm December 4, 2019

    了解下指针原理就懂了

  2. 36加盟网
    36加盟网 October 27, 2019

    在PHP7中, 一些类型是直接存储在zval中的, 比如IS_LONG

  3. Kobe
    Kobe September 26, 2019

    为什么都不更新了呢

  4. nanasaki
    nanasaki September 12, 2019

    $a = 1;
    $b = &$a;
    $c = $b;
    $c = 2;
    C 的值改变不会影响A
    但是
    $x = 1;
    $a = [1, &$x];
    $b = $a;
    $c = $b;
    $c[1] = 2;
    A 会变成 [1,2]
    而且只有$c[1] 和 $b[1]是指向同一个zval引用,其他下标的还是普通的值传递, 这是什么原理呢?

    • guanguan
      guanguan December 19, 2019

      用了&后,$a[1]被污染成 IS_REFERENCE类型,COW(写时复制)只对IS_STRING和IS_ARRAY起作用。当赋值$b = $a;$c=$b;的时候,$a与$b都是IS_ARRAY,赋值的结果是$b、$c都指向与 $a同一个zval,但是 当修改$c[1]($b[1]同理),会发现这是一个IS_REFERENCE,所以不会启用COW,而是直接对zend_reference指向的zval.value直接修改,故会影响到$a[1],$b[1]

  5. Kavin
    Kavin August 16, 2019

    最后,博客更新了

  6. cvv shop
    cvv shop June 23, 2019

    如果你做的一切正常,我认为你应该成功。

  7. 胡三
    胡三 June 1, 2019

    你这是5.X版本的吧

  8. wings io
    wings io May 24, 2019

    a very detailed and meticulous lesson, it really has a lot of values, I will learn a lot thanks

  9. Albert
    Albert March 4, 2019

    这次必须学习

  10. zhangzhenyu
    zhangzhenyu February 21, 2019

    这下明白那个5.4的经典问题了

  11. zhang
    zhang January 16, 2019

    PHP扩展中,解引用一个数组,然后zend_hash_next_index_insert下一个Long元素,最后第一次访问正常,第二次访问就溢出了。
    如果不是解引用后插入新的元素,而是修改元素,就一切正常。
    目前想不出原因,有谁能给我一个提示。

  12. bigbigbig
    bigbigbig January 5, 2019

    看了鸟哥发的博客受益匪浅

  13. Postal Zip Code
    Postal Zip Code December 21, 2018

    php5.6版本,突然服务爆卡,最后排错到系统内核问题,瞬间蒙了,求指点。(不能上图)是不是大量上传文件导致php消耗内存太多
    21.7% 【kernel】[k] __pv_queued_spin_lock_slowpath
    5.71% [kernel] [k] __do_page_fault
    5.48% libphp5.so [.] zendparse
    8核的系统 cpu 2.5HZ
    Tks

  14. cuicui
    cuicui December 19, 2018

    $x = array(‘a’, ‘b’);
    $y = &$x;
    $z = $x;
    xdebug_debug_zval(‘x’);
    xdebug_debug_zval(‘y’);
    xdebug_debug_zval(‘z’);
    x: (refcount=2, is_ref=1)=array (0 => (refcount=1, is_ref=0)=’a’, 1 => (refcount=1, is_ref=0)=’b’)
    y: (refcount=2, is_ref=1)=array (0 => (refcount=1, is_ref=0)=’a’, 1 => (refcount=1, is_ref=0)=’b’)
    z: (refcount=3, is_ref=0)=array (0 => (refcount=1, is_ref=0)=’a’, 1 => (refcount=1, is_ref=0)=’b’)
    ——————
    $x = “hello world”;
    $y = &$x;
    $z = $x;
    xdebug_debug_zval(‘x’);
    xdebug_debug_zval(‘y’);
    xdebug_debug_zval(‘z’);
    x: (refcount=2, is_ref=1)=’hello world’
    y: (refcount=2, is_ref=1)=’hello world’
    z: (refcount=0, is_ref=0)=’hello world’
    字符串/数字类型还是做拷贝了

  15. caythaomoc
    caythaomoc December 14, 2018

    学习了,谢谢。Tks

  16. internet fpt
    internet fpt December 14, 2018

    鸟哥的博客终于更新了 tks

  17. 400 icon
    400 icon December 14, 2018

    你好,想问问你这个性能测试工具是用什么来做的,请教一下. Tks

  18. Line 98
    Line 98 November 24, 2018

    能测试工具是用什2么来做的,请教一下。

  19. kubaoya
    kubaoya November 20, 2018

    整理的真好,谢谢分享

  20. 韶华倾负
    韶华倾负 November 13, 2018

    鸟哥,想看看PHP源码,从https://github.com/php/php-src.git clone了一份到电脑上,看了一些,自己想找的底层的方法没找到就随便看了,感觉有点瞎看,您有没什么建议能让在看源码时更有效率呢?

  21. xx
    xx November 12, 2018

    11

  22. Dean
    Dean September 26, 2018

    $a = array(1,2);
    $b = $a;
    $c = $a;
    xdebug_debug_zval(“a”); //(refcount=4, is_ref=0) 刷新之后refcount变成2
    // array (size=2)
    // 0 => (refcount=0, is_ref=0)int 1
    // 1 => (refcount=0, is_ref=0)int 2
    // 发生分离
    $b[] = 3;
    xdebug_debug_zval(“a”);//(refcount=3, is_ref=0) 刷新之后refcount变成2
    // array (size=2)
    // 0 => (refcount=0, is_ref=0)int 1
    // 1 => (refcount=0, is_ref=0)int 2
    问鸟哥一个问题,为什么数组$a的refcount为4,刷新过后又变成了2呢,下面的是3变成2,又是为什么?谢谢!

    • Dean
      Dean September 26, 2018

      我的php版本
      PHP Version 7.2.9-1+ubuntu16.04.1+deb.sury.org+1

    • ddd
      ddd November 12, 2018

      fff

      • dssf
        dssf November 12, 2018

        23423

  23. jane
    jane September 18, 2018

    test

  24. 1234
    1234 September 17, 2018

    ·21341243

  25. phython
    phython September 1, 2018

    鸟哥,看你博客更新的程序,PHP是不是快死掉了?

  26. Cvv shop
    Cvv shop August 21, 2018

    Thatnks you so much for this info! It’s very helpful!

  27. Tomato
    Tomato August 2, 2018

    可以可以可以

  28. gel titan
    gel titan June 26, 2018

    时隔两年,鸟哥居然更新微博了 thank you

  29. gel titan az
    gel titan az June 26, 2018

    时隔两年,鸟哥居然更新微博了 tks

  30. kí tự đặc biệt
    kí tự đặc biệt June 21, 2018

    这样的情况下,内核是立即分离拷贝 OR 增加引用计数?思路是怎么样的。tks

  31. eva
    eva June 21, 2018

    鸟哥的前排,有点紧张 tks

  32. Gel titan
    Gel titan June 21, 2018

    时隔两年,鸟哥居然更新微博了 tks

  33. 探路侠
    探路侠 June 6, 2018

    这是要满血复活的节奏呀

  34. 码客
    码客 May 26, 2018

    是学习php内核的好资料啊~

  35. and
    and May 20, 2018

    nb

  36. 小白two
    小白two May 16, 2018

    请讨论跟本文相关的技术问题,不要发广告。这是对博主的尊重!

  37. kavience
    kavience May 4, 2018

    向laruence学习。

  38. 5G云网络计算是中国领先的云计算公司,采用前沿云计算技术,专注打造专业的公共云计算服务,5G云主机13年老品牌:虚拟主机、域名注册、VPS主机、云服务器等,5G云怎么样!30余万个虚拟主机网站及200余万个域名用户的共同选择!

  39. JOE
    JOE May 2, 2018

    来自新西兰的PHPer,鸟哥的文章很久没看到了!

  40. owenliang1990
    owenliang1990 April 26, 2018

    比较好奇的是,能不能不使用reference,而直接让一个zend_array内部嵌套自己,这样的话zend_array的refcount应该=2。
    例如:
    $a = [];
    $a[‘self’] = $a;
    这样的情况下,内核是立即分离拷贝 OR 增加引用计数?思路是怎么样的。

  41. zhanjh
    zhanjh April 24, 2018

    鸟哥好,各位大佬好,同事问我array()以及[]这两种数组定义的区别.我回答不上来.能解释一下吗?在底层方面有区别吗?还是就只是个语法糖?

  42. 西顿家居灯饰加盟
    西顿家居灯饰加盟 April 18, 2018

    被鸟个的魅力深深折服,鸟哥以后家里装修要买灯饰,记得找我

  43. 小白
    小白 April 14, 2018

    php5.6版本,突然服务爆卡,最后排错到系统内核问题,瞬间蒙了,求指点。(不能上图)是不是大量上传文件导致php消耗内存太多
    21.7% 【kernel】[k] __pv_queued_spin_lock_slowpath
    5.71% [kernel] [k] __do_page_fault
    5.48% libphp5.so [.] zendparse
    8核的系统 cpu 2.5HZ

    • Leo
      Leo October 8, 2018

      你好,想问问你这个性能测试工具是用什么来做的,请教一下。

  44. 肥皂锅
    肥皂锅 April 14, 2018

    鸟哥的前排,有点紧张

  45. hy
    hy April 13, 2018

    鸟哥的博客终于更新了

  46. Anonymous
    Anonymous April 13, 2018

    时隔两年,鸟哥居然更新微博了

  47. funsoul
    funsoul April 9, 2018

    沙发!!!

Comments are closed.