程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 網頁編程 >> PHP編程 >> 關於PHP編程 >> PHP源碼分析-變量的引用計數、寫時復制(Reference counting & Copy-on-Wr

PHP源碼分析-變量的引用計數、寫時復制(Reference counting & Copy-on-Wr

編輯:關於PHP編程

 

PHP語法中有兩種賦值方式:引用賦值、非引用賦值。

 

 

<?php 

    $a = 1; 

    $b = $a; // 非引用賦值  

    $c = &$b; // 引用賦值 

<?php

       $a = 1;

       $b = $a; // 非引用賦值

       $c = &$b; // 引用賦值

從表面看,通常會這樣認為:“引用賦值就是兩個變量對應同一個變量(在C中其實就是一個zval),非引用賦值則是直接產生的一個新的變量(zval),同時將值copy過來”。

這種認為在大部分情況下都是可以想通的。(#1)

 

 

但有些情況下則會顯得非常低效,例如:(#2)

 

 

<?php 

function print_arr($arr){//非引用傳遞  

    print_r($arr); 

 

$test_arr = array( 

        'a' =>   'a', 

        'b' =>   'b', 

        'c' =>   'c', 

        ... 

    );//這裡一個比較大的數組  

 

print_arr($test_arr);//第一次調用print_arr函數執行輸出  

print_arr($test_arr);//第二次調用print_arr函數執行輸出 

<?php

function print_arr($arr){//非引用傳遞

       print_r($arr);

}

 

$test_arr = array(

              'a'    =>   'a',

              'b'    =>   'b',

              'c'    =>   'c',

              ...

       );//這裡一個比較大的數組

 

print_arr($test_arr);//第一次調用print_arr函數執行輸出

print_arr($test_arr);//第二次調用print_arr函數執行輸出

如果按照上面的理解方式(#1),那麼執行兩次print_arr,並且是非引用的方式,則會產生兩個與$test_arr完全相同的新的變量,那麼將是非常低效的。

 

 

實際代碼在運行中,並不會產生兩個新的變量。因為PHP內核中已經幫助我們進行了優化。

 

 

具體如何實現的呢?這裡就要講到本文的要點:Reference counting & Copy-on-Write,正是采用引用計數、寫時復制這兩個機制得以優化。

 

 

 

 

 

在介紹這兩個機制前,先了解一個基本知識:PHP中的變量在內核中是如何表示的。

PHP中定義的變量都是以一個zval來表示的,zval的定義在Zend/zend.h中定義:

 

 

 

typedef struct _zval_struct zval;   

 

typedef union _zvalue_value { 

    long lval;                  /* long value */ 

    double dval;                /* double value */ 

    struct { 

        char *val; 

        int len; 

    } str; 

    HashTable *ht;              /* hash table value */ 

    zend_object_value obj; 

} zvalue_value; 

 

struct _zval_struct { 

    /* Variable information */ 

    zvalue_value value;     /* value */ 

    zend_uint refcount; 

    zend_uchar type;    /* active type */ 

    zend_uchar is_ref; 

}; 

typedef struct _zval_struct zval; 

 

typedef union _zvalue_value {

    long lval;                  /* long value */

    double dval;                /* double value */

    struct {

        char *val;

        int len;

    } str;

    HashTable *ht;              /* hash table value */

    zend_object_value obj;

} zvalue_value;

 

struct _zval_struct {

    /* Variable information */

    zvalue_value value;     /* value */

    zend_uint refcount;

    zend_uchar type;    /* active type */

    zend_uchar is_ref;

};

 

其中,refcount和is_ref就是實現引用計數、寫時復制這兩個機制的基礎。

refcount當前變量存儲引用計數,在zval初始創建的時候就為1。每增加一個引用,則refcount ++。當進行引用分離時,refcount--。

is_ref用於表示一個zval是否是引用狀態。zval初始化的情況下會是0,表示不是引用。

 

 

 

 

 

 

<?php 

$a;//a:refcount=1,is_ref=0, value=NULL;  

$a = 1; //a:refcount=2,is_ref=0, value=1;  

$b = $a;    //a,b:refcount=3,is_ref=0,value=1;  

$c = $a;    //a,b,c:refcount=4,is_ref=0,value=1;  

$d = &$c; //a,b:refcount=3,is_ref=0,value=1;    c,d:refcount=1, is_ref=1, value=1 

<?php

$a;//a:refcount=1,is_ref=0, value=NULL;

$a = 1;    //a:refcount=2,is_ref=0, value=1;

$b = $a;  //a,b:refcount=3,is_ref=0,value=1;

$c = $a;  //a,b,c:refcount=4,is_ref=0,value=1;

$d = &$c; //a,b:refcount=3,is_ref=0,value=1;      c,d:refcount=1, is_ref=1, value=1上面代碼的注釋,表示當執行這一行後,refcount與is_ref的變化.

 

 

 

 

Copy on Write

 

 

Php變量通過引用計數實現變量共享數據,那如果改變其中一個變量值呢?

 

當試圖寫入一個變量時,Zend若發現該變量指向的zval被多個變量共享,則為其復制一份ref_count為1的zval,並遞減原zval的refcount,這個過程稱為“zval分離”。可見,只有在有寫操作發生時zend才進行拷貝操作,因此也叫copy-on-write(寫時拷貝)

 

對於引用型變量,其要求和非引用型相反,引用賦值的變量間必須是捆綁的,修改一個變量就修改了所有捆綁變量。

 

 

<?php 

    $a=1; 

    $b=$a; 

<?php

       $a=1;

       $b=$a;執行過程中的內存結構圖:

 

\

 

 

<?php 

    $a=1; 

    $b=&a; 

<?php

       $a=1;

       $b=&a;執行過程中的內存結構圖:

 

\

 

從上可以看到,無論是引用、非引用,這種直接賦值都不會產生新的變量。

只是當是引用時,is_ref設置為1。當非引用時,is_ref設置為0。

讀寫復制,就是根據is_ref來進行變量分離的。

 

 

 

當is_ref=1時,是引用變量時,執行“引用下的變量分離”

 

 

 

<?php 

    $a = 1; 

    $b = $a; 

    $c = &$b; 

<?php

       $a = 1;

       $b = $a;

       $c = &$b;執行過程中的內存結構圖:

 

 

\

 

 

當is_ref=0時,是非引用變量時,執行“非引用下的變量分離”

 

 

 

<?php 

    $a = 1; 

    $b = &$a; 

    $c = $b; 

<?php

       $a = 1;

       $b = &$a;

       $c = $b;

執行過程中的內存結構圖:

 

 

\

 

 

只有真正在需要改變變量的值時,

回頭在看(#2)代碼,可以看到實際上,並沒有產生新的變量,始終是$test_arr的變量在輸出。所以,這也是為什麼很少看到在PHP中使用引用方式傳遞變量,卻仍然不會有性能問題的原因。

 

 

 


摘自 God's blog

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved