深入理解PHP核心 一

2022-10-06 10:24:08 字數 4011 閱讀 2442

最近,和乙個網友交流的時候,給我提了乙個非常奇怪的問題。那就是,在乙個運算中,加了乙個引用之後,發現效能慢了一萬倍。在我的腦海裡面,引用是乙個非常容易出錯的問題,特別是php裡面的引用,有非常多的陷阱。因為,以前專門研究過這一塊php的源**,所以,我可以比較清晰的解析引用到底是怎麼一回事,希望,讀了我這篇文章,能徹底理解這個問題。如果,有任何疑問,或者有一些你想了解的問題,可以給我留言。

先來看一段**:

class reffertest

function reffer($key)

function noreffer($key)

function test()

$t2 = microtime(true) - $t1;

var_dump("reffer: " . round($t2, 4));

$t1 = microtime(true);

for ($i = 0; $i < 5000; $i++)

$t2 = microtime(true) - $t1;

var_dump("noreffer: " . round($t2, 4)); }}

$test = new reffertest();

$test->test();

如果你完這個**,能說出,為了reffer 和程式設計客棧 noreffer會差一萬倍的效能,那下面的也就沒有必要往下看了。這篇部落格針對的是,php的新手。你可以執行一下這個**試試看,的確差了一萬倍。當然,那個網友遇到的問題的**要比上面的複雜,上面的**是我為了說明問題,特意簡化的。或許你已經從**裡面看出問題了,但是,至於為什麼會這樣。我想,還是有必要分析一下。這樣,以後,在使用php的時候,才不會犯相同的錯誤。

php為了減少複製,採用了一種copy on writer的機制。我想,這是一種非常常見的機制,你也一定聽說過。比如,gcc 的 stl string 的實現,就是採用這樣的機制,字串賦值,不是真正的複製,而且,在修改的時候才會進行複製。我們先來舉個最簡單的例子:

$a = str_repeat("", );

$b = $a;

$a = "";

$a 是乙個非常程式設計客棧大的字串,如果 $b = $a 的時候,進行複製,那要耗費很多記憶體 和 cpu,這樣非常的不划算,萬一,下面的**並不修改$a 和 $b 那複製根本沒有必要。當然,$a 在後面又被修改了,這個時候,必須進行複製了,否則就不符合邏輯了。但是,現在問題來了,怎麼知道,$a 在修改的時候,要進行複製呢,必須要有這樣乙個標記。方法就是採用引用計數。引用計數還被用來進行記憶體的管理。

基本的流程是這樣的:

1: 建立乙個變數,可以儲存 10000 個 0 的這樣乙個字串。

2: 建立乙個變數符號 a ,這個變數符號引用 這個變數。注意,變數符號 和 變數不是一回事情,這兩者是分離的。

如果從c語言的角度來說,php大概完成這樣一件事情:

char *varname = "a";

size_t varname_len = strlen(varname);

zend_hash_add(eg(active_symbol_table), varname, varname_len + , &var, sizeof(zval*), null);

active_symbol_table 是php的乙個符號表,所有能訪問到的變數都在這個裡面,他是乙個雜湊表。var 這個變數,儲存了 10000 個 0 這個字串。而且是zval的結構,zval的結構如下:

typedef struct _zval_struct zval;

typedef union _zvalue_value str;

hashtable *ht;

zend_object_value obj;

} zvalue_value;

zvalue_value 是乙個聯合,可以儲存 long, double, 字串,雜湊表(php array),還有就是 物件。也就是所有的php的型別。 zval 其實 就是 對 zvalue_value ,加入了型別type 和 引用is_ref,引用計數refcount三個功能。這就是php中的普通變數。要是用php做比較大型的東西,就會發現,記憶體占用非常厲害。就是因為,他乙個變數 不是 傳統c語言的那個變數了,它加了很多東西。

好了,第一句完成了,下面是第二句。第二句很簡單,會產生乙個新的變數符號b,把他加入 active_symbol_table ,但是不會增加新的乙個變數,而只是,refcount++。賦值就完成了。如圖:

首先我們要注意的是,a ,b 只是乙個符號,他是active_symbol_table 表裡面的乙個key,都有乙個指標指向乙個zval,所以,a 和b 在 c語言層面上是完全一致的。我們就得出php變數第一定律:

php變數第一定律:如果兩個變數指向同乙個zval,那麼這兩個變數是無差別的。也就是說,任何對a 的操作 相對b 都是對稱的。這裡的對稱,是這樣理解的。就是鏡子中的你,而不是等同。比如,對 a 進行 賦值,a 就會產生 copy。同樣的,如果對b進行賦值,也會進行相同的操作,那就是b產生乙個copy。也就是說,a 和b的行為是一樣的。

第三句,當writer發生的時候,php會判斷一下refcount 是否大於2,如果大於2,那麼就複製一下zval,然後,把原來那個zval refcount--。這就是copy on writer 的全部了,你一定覺得,這一切你都是非常的熟悉,你都懂。

但是,php不僅僅是copy on writer 這樣簡單,它還有乙個引用的問題。引入引用的概念,這樣,問題就變的有些複雜了。因為,引用這個標記,意思就是說,writer 的時候,你也不需要複製。這樣,會修改原來的那個變數。從我們在學校裡面以前經常學習的哲學上來說,這是一對矛盾。他們是對立的,又是統一的,各有各的用處。所謂,存在的就是合理的。

好,下面我們來看看這對矛盾,我們只考慮兩種組合的情況。多種組合都是類似的。兩種組合的話,就是賦值在前,引用在後。

或者  引用在前,賦值在後。我們會分別討論,先來看:就是賦值在前,引用在後的情況。

$a = ;

$b = $a;

$c = &$a;

$b = $a, 是copy on writer 行為的 賦值。而 $c 和 $a 是引用賦值。我們假設在上面這樣的情況下,我們可以用乙個zval表示,也就是不需要複製,那麼情況是這樣的:

根據我們的php變數第一定律,那,就是說,a,b,c的操作是對稱的,但是非常明顯,對 b 操作要產生複製行為,而對a操作不會產生複製,操作行為不相同,和第一定律矛盾。也就是說,要使得上面的操作沒有矛盾,必須,進行分離。分離的原則就是,誰製造矛盾,誰複製。顯然是 第三句話,$c = &$a; 在製造矛盾。所以,內部變數的複製過程如下圖:

上面情況是賦值在前,引用在後的情況。還有一種情況是,引用在前賦值在後:

$a = ;

$b = &$a;

$c = $a;

按照php變數的第一定律,a,b,c 必須進行分離,才能保證定律的正確。可以發現,b 和 a 明顯是一夥人,就是說,b 和 a 的操作是對稱的,他們可以指向同乙個zval ,而c 的行為和 a,b 不一樣,改變c 需要進行複製。看到這裡,我想,如果你看懂了的話,為什麼剛開始,貼出來的那段**的,那個兩個count差異如此之大,你也應該明白了。當我和那個網友討論的時候,它最後說,那這樣的話,php設計的不好,我完全可以,$c先不進行複製,等c被write 了,再進行複製。看來要說懂乙個東西,還是一件很難的事情,好好想想那個php第一定律吧。你可以假設不進行分離,c指向www.cppcns.com同乙個zval,所以,c 和 a,b的行為是一樣的,是is_ref = 1,所以,c 不會進行複製。最後一種內部執**況可以用下圖表示:

我以前也進行搞混這個引用,現在,你可以用那個第一定律來分析所有的情況了。php核心分析的文章,以後我還會寫一些,如果你想深入了解php的某些方面,可以給我留言。

最後再補充一點,也是乙個隱性的錯誤。

function count_bigarray()

這裡,沒有顯示的引用,但是這裡隱藏了乙個引用。php會自動建立乙個引用全域性變數 $bigarray 的**,如果你在這裡使用count,那麼這個效率會非常的慢。最好直接通過$global 陣列進行引用。

本文標題: 深入理解php核心(一)

本文位址: /wangluo/php/134780.html

深入理解php核心

第二章 使用者 的執行 第三節 zend引擎與指令碼執行 第四節 小結 第三章 變數及資料型別 第二節 常量 第三節 預定義變數 第四節 靜態變數 第五節 型別提示的實現 第六節 變數的生命週期 第七節 資料型別轉換 第八節 小結 第四章 函式的實現 第二節 函式的定義,引數及返回值 第三節 函式的...

深入理解php核心

第二章 使用者 的執行 第三節 zend引擎與指令碼執行 第四節 小結 第三章 變數及資料型別 第二節 常量 第三節 預定義變數 第四節 靜態變數 第五節 型別提示的實現 第六節 變數的生命週期 第七節 資料型別轉換 第八節 小結 第四章 函式的實現 第二節 函式的定義,引數及返回值 第三節 函式的...

深入理解Linux核心 核心同步

核心基本的同步機制 搶占核心的主要特點 乙個在核心態執行的程序,可能在執行核心函式期間被另外乙個程序取代。核心搶占 linux 2.6允許使用者在編譯核心的時候配置十分啟用 程序臨界區 每個程序中訪問臨界資源 一次僅允許乙個程序使用的共享資源 的那段 稱為臨界區。優化屏障 保證編譯程式不會混淆放在原...