美文网首页
php变量分离性能问题的浅析

php变量分离性能问题的浅析

作者: 羽霖z | 来源:发表于2018-09-16 01:07 被阅读0次

    php的copy on write,指的是变量发生改变时内存的分离机制,“写”不能简单的理解为赋值,而是必须满足变量的改变。
    我们知道php的变量是由 zval实现的,下面是php5系列zval的定义:

    typedef union _zvalue_value {
     long lval;                  
     double dval;                
     struct {
         char *val;
         int len;
     } str;
     HashTable *ht;              
     zend_object_value obj;
     zend_ast *ast;
    } zvalue_value;
    
    struct _zval_struct {
     /* Variable information */
     zvalue_value value;     /* value */
     zend_uint refcount__gc;
     zend_uchar type;    /* active type */
     zend_uchar is_ref__gc;
    };
    

    zval结构中,refcount是用来记录指向变量容器(zval)的符号个数,之所以有此设计,也是为了更加高效的利用内存。那写时分离到底是如何进行的呢?我们看下面的例子:

    $val = 1;
    debug_zval_dump($val);echo "<br><br>";
    
    $val2 = $val;
    debug_zval_dump($val);echo "<br>";
    debug_zval_dump($val2);echo "<br><br>";
    
    $val3 = $val;
    debug_zval_dump($val);echo "<br>";
    debug_zval_dump($val2);echo "<br>";
    debug_zval_dump($val3);echo "<br><br>";
    

    最终结果如下:

    long(1) refcount(2) 
    
    long(1) refcount(3) 
    long(1) refcount(3) 
    
    long(1) refcount(4) 
    long(1) refcount(4) 
    long(1) refcount(4) 
    
    

    通过debug_zval_dump()函数吧变量在zend内部的表示输出出来,如果安装了xdebug,可以使用xdebug_debug_zval()函数达到类似的效果, 我们可以看到,$val的引用计数在不断增加,实际上$val2 = $val;$val3=$val...在执行的时候并没有发生内存复制,而是这几个变量的引用指向同一块内存地址。

    接着看下面的例子:

    $val = 1;
    debug_zval_dump($val);echo "<br><br>";
    
    $val2 = $val;
    debug_zval_dump($val);echo "<br>";
    debug_zval_dump($val2);echo "<br><br>";
    
    $val3 = $val;
    debug_zval_dump($val);echo "<br>";
    debug_zval_dump($val2);echo "<br>";
    debug_zval_dump($val3);echo "<br><br>";
    
    $val3 = 2;
    debug_zval_dump($val);echo "<br>";
    debug_zval_dump($val2);echo "<br>";
    debug_zval_dump($val3);echo "<br>";
    

    最终的结果为:

    long(1) refcount(2) 
    
    long(1) refcount(3) 
    long(1) refcount(3) 
    
    long(1) refcount(4) 
    long(1) refcount(4) 
    long(1) refcount(4) 
    
    long(1) refcount(3) 
    long(1) refcount(3) 
    long(2) refcount(2) 
    

    我们可以看到$val3被重新赋值之后,$val的引用计数减少了1,$val3被分离出来独占一块内存,$val和 $val2``` 继续指向统一内存地址。从这个过程中,我们大体看到分离的过程了。
    当与引用结合之后,情况会有所不同

    片段A:
    $val = 1;
    $val2 = $val;
    echo "<pre>";debug_zval_dump($val);echo "</pre>";
    $val3 = $val;    //此处不会分离
    

    所有的变量在未改变之前都是使用一个值,所以没有发生分离。后面谁改变就把谁分离出去,仍然不影响语义。

    片段B:
    $val = 1;
    $val2 = &$val;
    echo "<pre>";debug_zval_dump($val);echo "</pre>";
    $val3 = $val;    //此处会分离
    

    语义上是 $val2$val 共享一个值,但val3单独一块内存,如果此处第四行仍然没有分离的话,语义会产生错误。此处第三行与上面第三行的区别在于$val的zval的is_ref变成了1,这时php并没有启动写时复制机制,而是直接进行复制了。也就是说向一个is_ref=0的变量要值,不会直接复制,在变量改变时才会复制。向一个is_ref=1的变量要值的时候,会直接触发复制。

    片段C
    function du($arg){}
    $val = range(1,10);
    $b = $val;
    $start = microtime(true);
    for($i=0;$i<=100000;$i++) {
       du($val); 
    }
    printf("time: %ss\n", microtime(true) - $start);
    
    

    结果:

    time: 0.042001962661743s

    片段D
    function du($arg){}
    $val = range(1,10);
    $b = &$val;
    $start = microtime(true);
    for($i=0;$i<=100000;$i++) {
       du($val); 
    }
    printf("time: %ss\n", microtime(true) - $start);
    
    

    结果:
    time: 0.17601108551025s

    上面片段D代码之所以比片段C会消耗较更长的时间,就是因为执行d方法调用的时候,所经历的流程和片段B类似(d函数内部接收了参数的传值,并立刻复制了一份);
    在php7发布之后,zval采用了新的机制,这个地方的性能问题已经不复存在。关于php7中是如何处理的,我们下回再讲。

    参考资料:
    http://php.net/manual/zh/language.references.pass.php
    http://www.laruence.com/2018/04/08/3170.html

    相关文章

      网友评论

          本文标题:php变量分离性能问题的浅析

          本文链接:https://www.haomeiwen.com/subject/iygtnftx.html