PHP內(nèi)核的示例分析-創(chuàng)新互聯(lián)

小編給大家分享一下PHP內(nèi)核的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

創(chuàng)新互聯(lián)-云計(jì)算及IDC服務(wù)提供商,涵蓋公有云、IDC機(jī)房租用、服務(wù)器托管、等保安全、私有云建設(shè)等企業(yè)級(jí)互聯(lián)網(wǎng)基礎(chǔ)服務(wù),咨詢熱線:18982081108

PHP作為一門(mén)簡(jiǎn)單而強(qiáng)大的語(yǔ)言,能夠提供很多Web適用的語(yǔ)言特性。從實(shí)踐出發(fā),繼弱類(lèi)型變量原理探究后,本文繼續(xù)帶領(lǐng)大家深入理解php內(nèi)核。

 最近,和一個(gè)網(wǎng)友交流的時(shí)候,給我提了一個(gè)非常奇怪的問(wèn)題。那就是,在一個(gè)運(yùn)算中,加了一個(gè)引用之后,發(fā)現(xiàn)性能慢了一萬(wàn)倍。在我的腦海里面,引用是一個(gè)非常容易出錯(cuò)的問(wèn)題,特別是PHP里面的引用,有非常多的陷阱。因?yàn)?,以前?zhuān)門(mén)研究過(guò)這一塊PHP的源代碼,所以,我可以比較清晰的解析引用到底是怎么一回事,希望,讀了我這篇文章,能徹底理解這個(gè)問(wèn)題。如果,有任何疑問(wèn),或者有一些你想了解的問(wèn)題,可以給我留言。

先來(lái)看一段代碼:

class RefferTest
{
 private $data;
 private $testKey;
 function __construct()
 {
  $key = "hello";
  $this->data[$key] = range(0, 10000);
  $this->testKey = $key;
 }
 function reffer($key)
 {
  $reffer = &$this->data[$key];
  return count($reffer);
 }
 function noreffer($key)
 {
  return count($this->data[$key]);
 }
 function test()
 {
  $t1 = microtime(true);
  for ($i = 0; $i < 5000; $i++)
  {
   $this->reffer($this->testKey);
  }
  $t2 = microtime(true) - $t1;
  var_dump("reffer: " . round($t2, 4));
  $t1 = microtime(true);
  for ($i = 0; $i < 5000; $i++)
  {
   $this->noreffer($this->testKey);
  }
  $t2 = microtime(true) - $t1;
  var_dump("noreffer: " . round($t2, 4));
 }
}
$test = new RefferTest();
$test->test();

 如果你完這個(gè)代碼,能說(shuō)出,為了reffer 和 noreffer會(huì)差一萬(wàn)倍的性能,那下面的也就沒(méi)有必要往下看了。這篇博客針對(duì)的是,PHP的新手。你可以運(yùn)行一下這個(gè)代碼試試看,的確差了一萬(wàn)倍。當(dāng)然,那個(gè)網(wǎng)友遇到的問(wèn)題的代碼要比上面的復(fù)雜,上面的代碼是我為了說(shuō)明問(wèn)題,特意簡(jiǎn)化的?;蛟S你已經(jīng)從代碼里面看出問(wèn)題了,但是,至于為什么會(huì)這樣。我想,還是有必要分析一下。這樣,以后,在使用PHP的時(shí)候,才不會(huì)犯相同的錯(cuò)誤。

PHP為了減少?gòu)?fù)制,采用了一種copy on writer的機(jī)制。我想,這是一種非常常見(jiàn)的機(jī)制,你也一定聽(tīng)說(shuō)過(guò)。比如,gcc 的 stl string 的實(shí)現(xiàn),就是采用這樣的機(jī)制,字符串賦值,不是真正的復(fù)制,而且,在修改的時(shí)候才會(huì)進(jìn)行復(fù)制。我們先來(lái)舉個(gè)最簡(jiǎn)單的例子:

 $a = str_repeat("", );
  $b = $a;
  $a[] = "";

$a 是一個(gè)非常大的字符串,如果 $b = $a 的時(shí)候,進(jìn)行復(fù)制,那要耗費(fèi)很多內(nèi)存 和 cpu,這樣非常的不劃算,萬(wàn)一,下面的代碼并不修改$a 和 $b 那復(fù)制根本沒(méi)有必要。當(dāng)然,$a 在后面又被修改了,這個(gè)時(shí)候,必須進(jìn)行復(fù)制了,否則就不符合邏輯了。但是,現(xiàn)在問(wèn)題來(lái)了,怎么知道,$a 在修改的時(shí)候,要進(jìn)行復(fù)制呢,必須要有這樣一個(gè)標(biāo)記。方法就是采用引用計(jì)數(shù)。引用計(jì)數(shù)還被用來(lái)進(jìn)行內(nèi)存的管理。

基本的流程是這樣的:

1: 創(chuàng)建一個(gè)變量,可以保存 10000 個(gè) 0 的這樣一個(gè)字符串。

2: 創(chuàng)建一個(gè)變量符號(hào) a ,這個(gè)變量符號(hào)引用 這個(gè)變量。注意,變量符號(hào) 和 變量不是一回事情,這兩者是分離的。

如果從C語(yǔ)言的角度來(lái)說(shuō),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的一個(gè)符號(hào)表,所有能訪問(wèn)到的變量都在這個(gè)里面,他是一個(gè)哈希表。var 這個(gè)變量,保存了 10000 個(gè) 0 這個(gè)字符串。而且是zval的結(jié)構(gòu),zval的結(jié)構(gòu)如下:

typedef struct _zval_struct {
 zvalue_value value;
 zend_uint refcount;
 zend_uchar type;
 zend_uchar is_ref;
} zval;
typedef union _zvalue_value {
 long lval;
 double dval;
 struct {
  char *val;
  int len;
 } str;
 HashTable *ht;
 zend_object_value obj;
} zvalue_value;

 zvalue_value 是一個(gè)聯(lián)合,可以保存 long, double, 字符串,哈希表(PHP Array),還有就是 對(duì)象。也就是所有的PHP的類(lèi)型。 zval 其實(shí) 就是 對(duì) zvalue_value ,加入了類(lèi)型type 和 引用is_ref,引用計(jì)數(shù)refcount三個(gè)功能。這就是PHP中的普通變量。要是用PHP做比較大型的東西,就會(huì)發(fā)現(xiàn),內(nèi)存占用非常厲害。就是因?yàn)椋粋€(gè)變量 不是 傳統(tǒng)C語(yǔ)言的那個(gè)變量了,它加了很多東西。

好了,第一句完成了,下面是第二句。第二句很簡(jiǎn)單,會(huì)產(chǎn)生一個(gè)新的變量符號(hào)b,把他加入 active_symbol_table ,但是不會(huì)增加新的一個(gè)變量,而只是,refcount++。賦值就完成了。如圖:

PHP內(nèi)核的示例分析

首先我們要注意的是,a ,b 只是一個(gè)符號(hào),他是active_symbol_table 表里面的一個(gè)key,都有一個(gè)指針指向一個(gè)zval,所以,a 和b 在 C語(yǔ)言層面上是完全一致的。我們就得出PHP變量第一定律:

PHP變量第一定律:如果兩個(gè)變量指向同一個(gè)zval,那么這兩個(gè)變量是無(wú)差別的。也就是說(shuō),任何對(duì)a 的操作 相對(duì)b 都是對(duì)稱(chēng)的。這里的對(duì)稱(chēng),是這樣理解的。就是鏡子中的你,而不是等同。比如,對(duì) a 進(jìn)行 賦值,a 就會(huì)產(chǎn)生 copy。同樣的,如果對(duì)b進(jìn)行賦值,也會(huì)進(jìn)行相同的操作,那就是b產(chǎn)生一個(gè)copy。也就是說(shuō),a 和b的行為是一樣的。

第三句,當(dāng)writer發(fā)生的時(shí)候,PHP會(huì)判斷一下refcount 是否大于2,如果大于2,那么就復(fù)制一下zval,然后,把原來(lái)那個(gè)zval refcount--。這就是copy on writer 的全部了,你一定覺(jué)得,這一切你都是非常的熟悉,你都懂。

但是,PHP不僅僅是copy on writer 這樣簡(jiǎn)單,它還有一個(gè)引用的問(wèn)題。引入引用的概念,這樣,問(wèn)題就變的有些復(fù)雜了。因?yàn)?,引用這個(gè)標(biāo)記,意思就是說(shuō),writer 的時(shí)候,你也不需要復(fù)制。這樣,會(huì)修改原來(lái)的那個(gè)變量。從我們?cè)趯W(xué)校里面以前經(jīng)常學(xué)習(xí)的哲學(xué)上來(lái)說(shuō),這是一對(duì)矛盾。他們是對(duì)立的,又是統(tǒng)一的,各有各的用處。所謂,存在的就是合理的。

好,下面我們來(lái)看看這對(duì)矛盾,我們只考慮兩種組合的情況。多種組合都是類(lèi)似的。兩種組合的話,就是賦值在前,引用在后。

或者  引用在前,賦值在后。我們會(huì)分別討論,先來(lái)看:就是賦值在前,引用在后的情況。


  $a = ;
   $b = $a;
   $c = &$a;

$b = $a, 是copy on writer 行為的 賦值。而 $c 和 $a 是引用賦值。我們假設(shè)在上面這樣的情況下,我們可以用一個(gè)zval表示,也就是不需要復(fù)制,那么情況是這樣的:

PHP內(nèi)核的示例分析

根據(jù)我們的PHP變量第一定律,那,就是說(shuō),a,b,c的操作是對(duì)稱(chēng)的,但是非常明顯,對(duì) b 操作要產(chǎn)生復(fù)制行為,而對(duì)a操作不會(huì)產(chǎn)生復(fù)制,操作行為不相同,和第一定律矛盾。也就是說(shuō),要使得上面的操作沒(méi)有矛盾,必須,進(jìn)行分離。分離的原則就是,誰(shuí)制造矛盾,誰(shuí)復(fù)制。顯然是 第三句話,$c = &$a; 在制造矛盾。所以,內(nèi)部變量的復(fù)制過(guò)程如下圖:

PHP內(nèi)核的示例分析

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

 $a = ;
   $b = &$a;
   $c = $a;

按照PHP變量的第一定律,a,b,c 必須進(jìn)行分離,才能保證定律的正確??梢园l(fā)現(xiàn),b 和 a 明顯是一伙人,就是說(shuō),b 和 a 的操作是對(duì)稱(chēng)的,他們可以指向同一個(gè)zval ,而c 的行為和 a,b 不一樣,改變c 需要進(jìn)行復(fù)制??吹竭@里,我想,如果你看懂了的話,為什么剛開(kāi)始,貼出來(lái)的那段代碼的,那個(gè)兩個(gè)count差異如此之大,你也應(yīng)該明白了。當(dāng)我和那個(gè)網(wǎng)友討論的時(shí)候,它最后說(shuō),那這樣的話,PHP設(shè)計(jì)的不好,我完全可以,$c先不進(jìn)行復(fù)制,等c被write 了,再進(jìn)行復(fù)制??磥?lái)要說(shuō)懂一個(gè)東西,還是一件很難的事情,好好想想那個(gè)PHP第一定律吧。你可以假設(shè)不進(jìn)行分離,c指向同一個(gè)zval,所以,c 和 a,b的行為是一樣的,是is_ref = 1,所以,c 不會(huì)進(jìn)行復(fù)制。最后一種內(nèi)部執(zhí)行情況可以用下圖表示:

PHP內(nèi)核的示例分析

我以前也進(jìn)行搞混這個(gè)引用,現(xiàn)在,你可以用那個(gè)第一定律來(lái)分析所有的情況了。PHP內(nèi)核分析的文章,以后我還會(huì)寫(xiě)一些,如果你想深入了解PHP的某些方面,可以給我留言。

最后再補(bǔ)充一點(diǎn),也是一個(gè)隱性的錯(cuò)誤。

function count_bigarray()
{
 global $bigarray;
 return count($bigarray);
}

這里,沒(méi)有顯示的引用,但是這里隱藏了一個(gè)引用。PHP會(huì)自動(dòng)創(chuàng)建一個(gè)引用全局變量 $bigarray 的代碼,如果你在這里使用count,那么這個(gè)效率會(huì)非常的慢。好直接通過(guò)$GLOBAL 數(shù)組進(jìn)行引用。

以上是“PHP內(nèi)核的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!

當(dāng)前文章:PHP內(nèi)核的示例分析-創(chuàng)新互聯(lián)
地址分享:http://muchs.cn/article26/dpipjg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供Google、面包屑導(dǎo)航、外貿(mào)建站、關(guān)鍵詞優(yōu)化、手機(jī)網(wǎng)站建設(shè)、定制網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都做網(wǎng)站