深入PHP使用技巧之變量

來源:本站原創 專業IT吐槽 超過548 views圍觀 0條評論

眾所周知,PHP與其他腳本語言一樣,屬于弱變量類型的語言。同時PHP本身也是通過C語言來實現。本文主要介紹PHP內部是如何實現弱變量類型的,并且據此分析在PHP開發中需要注意的一些使用技術。其中會重點分析PHP中的copy on write機制和引用相關方面的話題。 本章節屬于《深入PHP使用技巧》的第一部分。

如何實現弱變量

在了解PHP實現弱變量類型之前,可以先思考下:如何通過C/C++來實現弱變量類型的效果呢?

這個問題我在BIT培訓課上基本上有兩種答案:

方法1:采用C++的繼承機制。首先定義一個基礎類型

1
Class Var

2
{

3
}

然后基于Var,派生出不同的子類型IntVar/FloatVar/StringVar等等。
方法2:基于C語言的 Struct。其中一個字段用于標識類型,另外一個字段用于存儲數據,由于數據要是各種類型,所以通常需要采用指針

比如:

1
struct var {

2
Int type;

3
Void *data;

4
};

兩種思路本身并沒有太大區別,也都基本上能夠滿足需求。在PHP中采用了第二種思路,并且做了比較多的優化。在PHP中,所有的變量都會對應同一種類型zval,其中zval也就是struct _zval_struct,具體定義如下:

01
typedef union _zvalue_value {

02
long lval;                  /* long value */

03
double dval;                /* double value */

04
struct {

05
char *val;

06
int len;

07
} str;

08
HashTable *ht;              /* hash table value */

09
zend_object_value obj;

10
} zvalue_value;

11
struct _zval_struct {

12
/* Variable information */

13
zvalue_value value;     /* value */

14
zend_uint refcount;

15
zend_uchar type;    /* active type */

16
zend_uchar is_ref;

17
};

從zval可以看出,PHP在細節方面的確做了不少優化的功夫。

1.zend_uchar type。采用uchar節省內存。
2.zvalue_value value; 采用union來替換void *,這樣同樣能節省空間,并且比void *更能表義清晰。
3.在字符串類型中,默認保留了字符串的長度。這樣很容易做到字符串的二進制安全,并且在計算字符串長度的時候不需要進行掃描。
觀察PHP弱變量的實現,也會有以下疑惑:

1.為什么會沒有int類型呢?其實在PHP中是有的,只是說默認int數據就保存在long中。
2.資源類型咋表現的呢?資源在PHP內部其實就是一數字。詳細后續會介紹。
3.refcount和is_ref是干嘛的呢?呵呵,這就是第二部分要介紹的了。

Reference counting & Copy-on-Write

PHP和其他語言類似,在其語法中有兩種賦值方式:引用賦值和非引用賦值(普通的==賦值)。

1
<?php

2
$a = 1;

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

4
$c = &$a;//引用賦值

5
?>

引用賦值和非引用賦值在PHP內部是如何實現的呢?一種通常的認識是:“引用賦值就是兩個變量對應同一個Zval,非引用賦值則是直接產生一個新的zval,同時把對應的值直接copy過來。”也就是該代碼的內存結構如下:

(該圖是大多數人認為的PHP內存結構,是錯誤的)

這樣的確能夠滿足大部分情況下的需求,但顯然不是最佳的解決方案,尤其是在內存管理上,比如說以下代碼就會顯得非常的低效。

1
<?php

2
$arr = array(...);//定義一個非常大的PHP數組

3
myfunc($arr);//每一個函數調用都是一次隱性的非引用賦值

4
myfunc($arr);

5
?>

因為每次函數調用會進行一次內存dump,而大內存的內存dump是非常耗CPU的。在C語言中,一種解決方案是采用指針,所有函數調用盡量傳遞指針。的確很靈活高效,但也很難維護~指針可以說是C語言程序員心頭的痛(當然也是福~^_^)。還有一種更高級更有效的方法是采用引用計數(Reference counting)。

在PHP中,也可以采用引用來解決這樣的問題,但你見過采用在PHP中大量使用引用的嗎?顯然很少。

在PHP內核中,Zval的實現正是采用了引用計數的概念,說起引用計數就不得不談到copy-on-write 機制。這樣前面談到的refcount和is_ref就有作用了。

  • refcount:引用次數。在zval初始創建的時候就為1。每增加一個引用,則refcount ++。
  • is_ref:用于表示一個zval是否是引用狀態。zval初始化的情況下會是0,表示不是引用。

在Zend/Zend.h內部有一些關于ZVAL的宏定義,里面比較清晰的解析了引用計數的一些規則,其中重點關注以下幾個宏定義

01
#define INIT_PZVAL(z)       \

02
(z)->refcount = 1;       \

03
(z)->is_ref = 0;

04
#define SEPARATE_ZVAL_IF_NOT_REF(ppzv)      \//非引用下的變量分離

05
if (!PZVAL_IS_REF(*ppzv)) {             \

06
SEPARATE_ZVAL(ppzv);                \

07
}

08
#define SEPARATE_ZVAL_TO_MAKE_IS_REF(ppzv)  \//非引用下的變量分離,并且設置引用

09
if (!PZVAL_IS_REF(*ppzv)) {             \

10
SEPARATE_ZVAL(ppzv);                \

11
(*(ppzv))->is_ref = 1;               \

12
}

13
#define SEPARATE_ARG_IF_REF(varptr) \     //引用下的變量分離

14
if (PZVAL_IS_REF(varptr)) { \

15
zval *original_var = varptr; \

16
ALLOC_ZVAL(varptr); \

17
varptr->value = original_var->value; \

18
varptr->type = original_var->type; \

19
varptr->is_ref = 0; \

20
varptr->refcount = 1; \

21
zval_copy_ctor(varptr); \

22
} else { \

23
varptr->refcount++; \

24
}

這里面談到兩個重要的概念:

1、非引用下的變量分離。
非引用下的變量分離,是指在一堆非引用變量中插入引用的情況下,在PHP內部進行的一種內存操作。以下面的列子來看:

1
$a = 1;

2
$b = $a;

3
$c = &$b;

在前兩句執行之后,內存結構如下圖

在第三句 $c = &$b;語句中則會執行“非引用下的變量分離。”,具體步驟是:

將b分離出來,同時把a對應的zval的refcount-1。
copy 出一個新的zval,并把zval的is_ref設置成1.
把C指向這個新的zval,同時refcount ++
最終效果如下圖:

2、引用下的變量分離。

引用下的變量分離,是指在一堆引用變量中進行一個非引用賦值操作,這個時候會直接執行copy內存的操作。

以下面的例子來說

1
$a = 1;

2
$b = &$a;

3
$c = $b;

在執行完前兩行后,PHP中內存結構如下:

在第三句,則會執行“引用下的變量分離”也就是真正的copy,最終內存結構如下圖

據此,基本上對PHP變量內部的一些原理比較清楚了,但還有一些需要注意點的:
1、PHP變量的引用計數特性,對于數組同樣也存在。但注意,對于key則不生效。(具體在后面章節會分析到。)
2、PHP變量中的對象比較特殊,在PHP5之后,默認都是采用引用賦值的方式。具體實現可以參考Zend_objects.*系列代碼。
3、對于分析PHP內部變量,推薦采用xdebug_debug_zval,而不要采用內置的debug_zval_dump。因為PHP內置的debug_zval_dump函數一方面無法處理is_ref,而且采用了引用的方式來處理,從而導致看到結果會有誤解。

使用技巧結論

據此可以得出分析出不少結論:

1、在PHP開發中不推薦采用引用。因為PHP內部對內存優化本身做了不少工作,引用不會帶來太多優化。(但注意推薦非強制)

2、在PHP中strlen是o(1)的。

李蕭明吐槽 只能說百度拿這么高工資還是有道理的。

文章出自:CCIE那點事 http://www.qdxgqk.live/ 版權所有。本站文章除注明出處外,皆為作者原創文章,可自由引用,但請注明來源。 禁止全文轉載。
本文鏈接:http://www.qdxgqk.live/?p=915轉載請注明轉自CCIE那點事
如果喜歡:點此訂閱本站
  • 相關文章
  • 為您推薦
  • 各種觀點
?
暫時還木有人評論,坐等沙發!
發表評論

您必須 [ 登錄 ] 才能發表留言!

?
?
萌宠夺宝游戏