首頁 後端開發 PHP問題 PHP底層運作機制與原理該如何理解?看懂這篇文章就夠了

PHP底層運作機制與原理該如何理解?看懂這篇文章就夠了

Aug 24, 2019 am 09:51 AM
運作機制

PHP說簡單,但要精通也不是一件簡單的事。我們除了會使用之外,還得知道它底層的工作原理。

PHP是一種適用於web開發的動態語言。具體點說,就是一個用C語言實作包含大量元件的軟體框架。更狹義點看,可以把它認為是強大的UI框架。

了解PHP底層實現的目的為何?動態語言要用好首先得了解它,記憶體管理、框架模型值得我們借鑒,透過擴展開發實現更多更強大的功能,優化我們程式的效能。

1. PHP的設計概念及特點

多進程模型:由於PHP是多進程模型,不同請求間互不干涉,這樣保證了一個請求掛掉不會對全碟服務造成影響,當然,隨著時代發展,PHP也早已支援多執行緒模型。弱型別語言:和C/C 、Java、C#等語言不同,PHP是弱型別語言。一個變數的類型並不是一開始就確定不變,運行中才會確定並可能發生隱式或顯式的類型轉換,這種機制的靈活性在web開發中非常方便、高效,具體會在後面PHP變數中詳述。引擎(Zend) 元件(ext)的模式降低內部耦合。中間層(sapi)隔絕web server和PHP。文法簡單靈活,沒有太多規範。缺點導致風格混雜,但再差的程式設計師也不會寫出太離譜危害全局的程式。

2. PHP的四層體系

PHP的核心架構如下圖:

PHP底層運作機制與原理該如何理解?看懂這篇文章就夠了

從圖上可以看出,PHP從下到上是一個4層體系:

Zend引擎:Zend整體用純C實現,是PHP的核心部分,它將PHP程式碼翻譯(詞法、語法解析等一系列編譯過程)為可執行opcode的處理並實現相應的處理方法、實現了基本的資料結構(如hashtable、oo)、記憶體分配及管理、提供了相應的api方法供外部調用,是一切的核心,所有的外圍功能均圍繞Zend實現。 Extensions:圍繞著Zend引擎,extensions透過元件式的方式提供各種基礎服務,我們常見的各種內建函數(如array系列)、標準函式庫等都是透過extension來實現,使用者也可以根據需要實現自己的extension以達到功能擴充、效能最佳化等目的(如貼吧正在使用的PHP中間層、富文本解析就是extension的典型應用)。 Sapi:Sapi全名為Server Application Programming Interface,也就是服務端應用程式接口,Sapi透過一系列鉤子函數,使得PHP可以和外圍交互數據,這是PHP非常優雅和成功的一個設計,透過sapi成功的將PHP本身和上層應用解耦隔離,PHP可以不再考慮如何針對不同應用進行相容,而應用程式本身也可以針對自己的特徵實現不同的處理方式。上層應用:這就是我們平時編寫的P​​HP程序,透過不同的sapi方式得到各種各樣的應用模式,例如透過webserver實現web應用程式、在命令列下以腳本方式運行等等。

如果PHP是一輛車,那麼車的車架就是PHP本身,Zend是車的引擎(引擎),Ext下面的各種組件就是車的輪子,Sapi可以看做是公路,車可以跑在不同類型的公路上,而一次PHP程序的執行就是汽車跑在公路上。因此,我們需要:性能優異的引擎 合適的車輪 正確的跑道。

3. Sapi

如前所述,Sapi透過透過一系列的接口,使得外部應用可以和PHP交換資料並且可以根據不同應用特徵實現特定的處理方法,我們常見的一些sapi有:

apache2handler:這是以apache作為webserver,採用mod_PHP模式運行時的處理方式,也是現在應用最廣泛的一種。 cgi:這是webserver和PHP直接的另一種互動方式,也就是大名鼎鼎的fastcgi協議,在最近今年fastcgi PHP得到越來越多的應用,也是異步webserver所唯一支持的方式。 cli:命令列呼叫的應用模式

4. PHP的執行流程&opcode

#我們先來看看PHP程式碼的執行所經過的流程。

PHP底層運作機制與原理該如何理解?看懂這篇文章就夠了

從圖上可以看到,PHP實作了一個典型的動態語言執行過程:拿到一段程式碼後,經過詞法解析、語法解析等階段後,原始程式會被翻譯成一個指令(opcodes),然後ZEND虛擬機器順次執行這些指令完成操作。 PHP本身是用C來實現的,所以最後呼叫的也都是C的函數,實際上,我們可以把PHP看做是一個C開發的軟體。

PHP的執行的核心是翻譯出來的一條一條指令,也即opcode。

Opcode是PHP程式執行的最基本單位。一個opcode由兩個參數(op1,op2)、傳回值和處理函數組成。 PHP程式最終被翻譯為一組opcode處理函數的順序執行。

常見的幾個處理函數:

PHP

ZEND_ASSIGN_SPEC_CV_CV_HANDLER : 變數指派 ($a=$b)

ZEND_DO_FCALL_BY_NAME_SPEC_HANDLER:函數呼叫

ZEND_CONCAT_SPEC_CV_CV_HANDLER:字串拼接 $a.$b

##加法運算 $a 2

ZEND_IS_EQUAL_SPEC_CV_CONST

:判斷相等 $a==1

ZEND_IS_IDENTICAL_SPEC_CV_CONST

:判斷相等#5. HashTable — 核心資料結構

HashTable是zend的核心資料結構,在PHP裡面幾乎並用來實現所有常見功能,我們知道的PHP數組就是其典型應用,此外,在zend內部,如函數符號表、全域變數等也都是基於hash table來實作。

PHP的hash table具有以下特點:

1、支援典型的key->value查詢

2、可以當做陣列使用

3 、新增、刪除節點是O(1)複雜度

4、key支援混合型別:同時存在關聯數組和索引數組

#5、Value支援混合型別:array (“string” ,2332)

6、支援線性遍歷:如foreach

Zend hash table實現了典型的hash表散列結構,同時透過附加一個雙向鍊錶,提供了正向、反向遍歷數組的功能。其架構如下圖:


可以看到,在hash table中既有key->value形式的雜湊結構,也有雙向鍊錶模式,使得它能夠非常方便的支援快速查找和線性遍歷。 PHP底層運作機制與原理該如何理解?看懂這篇文章就夠了

雜湊結構:Zend的雜湊結構是典型的hash表模型,透過鍊錶的方式來解決衝突。需要注意的是zend的hash table是一個自增長的資料結構,當hash表數目滿了之後,其本身會動態以2倍的方式擴容並重新元素位置。初始大小均為8。另外,在進行key->value快速找到時候,zend本身也做了一些優化,透過空間換時間的方式加快速度。例如在每個元素中都會用一個變數nKeyLength來標識key的長度以作快速判定。

雙向鍊錶:Zend hash table透過一個鍊錶結構,實現了元素的線性遍歷。理論上,做遍歷使用單向鍊錶就夠了,之所以使用雙向鍊錶,主要目的是為了快速刪除,避免遍歷。 Zend hash table是一種複合型的結構,作為數組使用時,即支援常見的關聯數組也能夠作為順序索引數字來使用,甚至允許2者的混合。

PHP關聯陣列:關聯陣列是典型的hash_table應用。一次查詢過程經過以下幾步(從程式碼可以看出,這是一個常見的hash查詢過程並增加一些快速判定加速查找。):

PHP

getKeyHashValue h;
index = n & nTableMask;
Bucket *p = arBucket[index];
while (p) {
if ((p->h == h) & (p->nKeyLength == nKeyLength)) {
RETURN p->data;  
}
p=p->next;
}
RETURN FALTURE;
登入後複製
PHP索引數組:

索引數組就是我們常見的數組,透過下標存取。例如 $arr[0],Zend HashTable內部進行了歸一化處理,對於index類型key同樣分配了hash值和nKeyLength(為0)。內部成員變數nNextFreeElement就是目前被指派的最大id,每次push後自動加一。正是這種歸一化處理,PHP才能夠實現關聯和非關聯的混合。由於push操作的特殊性,索引key在PHP數組中先後順序並不是透過下標大小來決定,而是由push的先後決定。例如$arr[1] = 2; $arr[2] = 3;對於double型別的key,Zend HashTable會將他當做索引key處理

6. PHP變數

#

PHP是一門弱型別語言,本身不嚴格區分變項的型別。 PHP在變數申明的時候不需要指定型別。 PHP在程式運行期間可能會進行變數類型的隱示轉換。和其他強型別語言一樣,程式中也可以進行顯示的型別轉換。 PHP變數可以分為簡單型別(int、string、bool)、集合型別(array resource object)和常數(const)。以上所有的變數在底層都是同一種結構 zval。

Zval是zend中另一個非常重要的資料結構,用來識別並實作PHP變量,其資料結構如下:

PHP底層運作機制與原理該如何理解?看懂這篇文章就夠了

Zval主要由三部分組成:

type:指定了變數所述的型別(整數、字串、陣列等)

refcount&is_ref:用來實作引用計數(後面具體介紹)

value:核心部分,儲存了變數的實際資料

Zvalue是用來保存一個變數的實際資料。因為要儲存多種類型,所以zvalue是一個union,也因此實現了弱型別。

PHP變數型別與其實際儲存對應如下:

1、IS_LONG   -> lvalue

2、IS_DOUBLE -&gt ; dvalue

3、IS_ARRAY  -> ht

4、IS_STRING -> str

5 、IS_RESOURCE -> lvalue

引用計數在記憶體回收、字串操作等地方使用非常廣泛。 PHP中的變數就是引用計數的典型應用。 Zval的引用計數透過成員變數is_ref和ref_count實現,透過引用計數,多個變數可以共享同一份資料。避免頻繁拷貝帶來的大量消耗。

在進行賦值操作時,zend將變數指向相同的zval同時ref_count ,在unset操作時,對應的ref_count-1。只有ref_count減為0時才會真正執行銷毀操作。如果是引用賦值,則zend會修改is_ref為1。

PHP變數透過引用計數實現變數共享數據,那如果改變其中一個變數值呢?當試圖寫入一個變數時,Zend若發現該變數指向的zval被多個變數共享,則為其複製一份ref_count為1的zval,並遞減原zval的refcount,這個過程稱為「zval分離」。可見,只有在有寫入操作發生時zend才進行拷貝操作,因此也叫copy-on-write(寫時拷貝)

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

整數、浮點數是PHP中的基礎型別之一,也是簡單型變數。對於整數和浮點數,在zvalue中直接儲存對應的值。其型別分別是long和double。

從zvalue結構可以看出,對於整數型,和c等強型別語言不同,PHP是不區分int、unsigned int、long、long long等型別的,對它來說,整數只有一種類型也就是long。由此,可以看出,在PHP裡面,整數的值範圍是由編譯器位數來決定而不是固定不變的。

對於浮點數,類似整數,它也不區分float和double而是統一隻有double一種型別。

在PHP中,如果整數範圍越界了怎麼辦?這種情況下會自動轉換為double類型,這個一定要小心,很多trick都是由此產生。

和整數一樣,字元變數也是PHP中的基礎型別和簡單型變數。透過zvalue結構可以看出,在PHP中,字串是由指向實際資料的指標和長度結構體組成,這點和c 中的string比較類似。由於透過一個實際變數表示長度,和c不同,它的字串可以是2進位資料(包含),同時在PHP中,求字串長度strlen是O(1)運算。

在新增、修改、追加字串操作時,PHP都會重新分配記憶體產生新的字串。最後,基於安全考慮,PHP在產生一個字串時結尾仍會加上

常見的字串拼接方式及速度比較:

假設有以下4個變數:$strA= '123'; $strB = '456'; $intA=123; intB=456;

現在對如下的幾個字串拼接方式做一個比較和說明:

#PHP

$res = $strA.$strB和$res = 「$strA$strB」

這種情況下,zend會重新malloc一塊記憶體並進行對應處理,其速度一般

$strA = $strA.$strB

這種是速度最快的,zend會在目前strA基礎上直接relloc,避免重複拷貝

#$res = $intA.$intB

這種速度較慢,因為需要做隱含的格式轉換,實際編寫程式中也應該注意盡量避免

$strA = sprintf ( “%s%s”,$strA.$strB);

這會是最慢的一種方式,因為sprintf在PHP中並不是一個語言結構,本身對於格式辨識和處理就需要耗費比較多時間,另外本身機制也是malloc。不過sprintf的方式最具可讀性,實際中可以根據具體情況靈活選擇。

PHP的陣列透過Zend HashTable來天然實作。

foreach操作如何實現?對一個陣列的foreach就是透過遍歷hashtable中的雙向鍊錶完成。對於索引數組,透過foreach遍歷效率比for高很多,省去了key->value的查找。 count操作直接呼叫HashTable->NumOfElements,O(1)操作。對於’123’這樣的字串,zend會轉換為其整數形式。 $arr[‘123’]和$arr[123]是等價的

資源型變數是PHP中最複雜的一種變量,也是一種複合型結構。

PHP的zval可以表示廣泛的資料類型,但是對於自訂的資料類型卻很難充分描述。由於沒有有效的方式描繪這些複合結構,因此也沒有辦法對它們使用傳統的操作符。要解決這個問題,只需要透過一個本質上任意的標識符(label)來引用指針,這種方式稱為資源。

在zval中,對於resource,lval作為指標來使用,直接指向資源所在的位址。 Resource可以是任意的複合結構,我們熟悉的mysqli、fsock、memcached等都是資源。

如何使用資源:

註冊:對於一個自訂的資料類型,要想將它作為資源。首先需要進行註冊,zend會為它指派全域唯一標示。取得一個資源變數:對於資源,zend維護了一個id->實際資料的hash_tale。對於一個resource,在zval中只記錄了它的id。 fetch的時候透過id在hash_table中找到具體的值返回。資源銷毀:資源的資料型態是多種多樣的。 Zend本身沒有辦法銷毀它。因此需要使用者在註冊資源的時候提供銷毀函數。當unset資源時,zend呼叫對應的函數完成析構。同時從全域資源表中刪除它。

資源可以長期駐留,不只是在所有引用它的變數超出作用域之後,甚至是在一個請求結束了並且新的請求產生之後。這些資源稱為持久資源,因為它們貫通SAPI的整個生命週期持續存在,除非刻意銷毀。很多情況下,持久化資源可以在一定程度上提高效能。例如我們常見的mysql_pconnect ,持久化資源透過pemalloc分配內存,這樣在請求結束的時候不會釋放。
對zend來說,對兩者本身並不區分。

PHP中的局部變數和全域變數是如何實現的?對於一個請求,任意時刻PHP都可以看到兩個符號表(symbol_table和active_symbol_table),其中前者用來維護全域變數。後者是指針,指向目前活動的變數符號表,當程式進入某個函數時,zend就會為它指派一個符號表x同時將active_symbol_table指向a。透過這樣的方式實現全域、局部變數的區分。

取得變數值:PHP的符號表是透過hash_table實現的,對於每個變數都分配唯一標識,取得的時候根據標識從表中找到對應zval返回。

函數中使用全域變數:在函數中,我們可以透過明確申明global來使用全域變數。在active_symbol_table中建立symbol_table中同名變數的引用,如果symbol_table中沒有同名變數則會先建立。

更多相關問題請上PHP中文網:PHP實戰影片教學

#

以上是PHP底層運作機制與原理該如何理解?看懂這篇文章就夠了的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

PHP核心的運作機制與實作原理詳解 PHP核心的運作機制與實作原理詳解 Nov 08, 2023 pm 01:15 PM

PHP是一種流行的開源伺服器端腳本語言,大量用於Web開發。它能夠處理動態資料以及控制HTML的輸出,但是,如何實現這一切?那麼,本文將會介紹PHP的核心運作機制和實作原理,並利用具體的程式碼範例,進一步說明其運作過程。 PHP原始碼解讀PHP原始碼是一個由C語言編寫的程序,經過編譯後產生可執行檔php.exe,而對於Web開發中使用的PHP,在執行時一般透過A

深度解析:Go語言中goroutine的本質與運作機制 深度解析:Go語言中goroutine的本質與運作機制 Mar 12, 2024 pm 03:39 PM

在Go語言中,goroutine是一種輕量級的線程,用於並發執行程式碼片段。與傳統的線程相比,goroutine更加高效,具有更低的記憶體消耗和更快的啟動速度。在本文中,我們將深度解析Go語言中goroutine的本質和運行機制,同時提供具體的程式碼範例來幫助讀者更好地理解。 1.Goroutine的本質在Go語言中,goroutine是由Go運行時管理的輕量級

探討Swoole中協程的運作機制 探討Swoole中協程的運作機制 Jun 13, 2023 am 10:27 AM

Swoole是一個基於PHP的協程框架,它的非同步IO表現非常出色。 Swoole的核心是協程,協程是一種比執行緒更輕量級的並發機制,可以在同一執行緒中切換任務來實現並發執行。本文將會探討Swoole中協程的運作機制。一、協程的概念協程,又稱微線程,是比線程更細粒度的並發機制。協程與執行緒的差異在於,協程透過時間片輪轉來實現任務切換,而執行緒則由作業系統調度器負責切換

了解Zend Framework中間件的運作機制與原理 了解Zend Framework中間件的運作機制與原理 Jul 28, 2023 pm 01:49 PM

了解ZendFramework中間件的運作機制和原理隨著網路的不斷發展,web應用程式的複雜性也不斷增加。為了解決這些問題,中間件的概念應運而生。中間件是一個非常重要的技術,在ZendFramework中也得到了廣泛的應用。本文將介紹ZendFramework中間件的運作機制和原理,並透過範例程式碼詳細說明。首先,什麼是中間件?中介軟體是一種可以對請

解析ApacheTomcat的工作原理與運作機制 解析ApacheTomcat的工作原理與運作機制 Jan 24, 2024 am 10:14 AM

ApacheTomcat是一個開源的JavaServlet容器,由Apache軟體基金會開發和維護。它是最受歡迎的用於Java應用程式開發的Servlet容器之一,廣泛用於企業級Web應用程式的部署。本文將詳細解析ApacheTomcat的原理及運作機制,並提供具體的程式碼範例。 Tomcat的架構ApacheTomcat採用了基於元件的架構,由多個模組組

深入解析Tomcat中間件的運作機制和內部工作原理 深入解析Tomcat中間件的運作機制和內部工作原理 Dec 28, 2023 pm 01:20 PM

解密Tomcat中間件的運作機制和內部運作原理摘要:Tomcat是一個廣泛用於JavaWeb應用程式的開源HTTP伺服器和Servlet容器。它提供了豐富的功能,如處理HTTP請求、管理網路應用程式和Servlet生命週期管理等。本文將深入探討Tomcat中間件的運作機制與內部運作原理,包括掌握Tomcat的核心元件、請求處理流程、類別載入機制、Servl

Linux核心功能詳解:五大部分的全面解讀 Linux核心功能詳解:五大部分的全面解讀 Mar 21, 2024 am 08:18 AM

Linux核心功能詳解:五大部分的全面解讀Linux核心是一個開源的作業系統內核,負責管理電腦的硬體資源,並提供行程管理、檔案系統和裝置驅動等功能。 Linux核心由許多不同的部分組成,每個部分都有特定的功能和責任。本文將對Linux核心的五大部分進行全面解讀,並提供具體的程式碼範例幫助讀者更好地理解。 1.進程管理進程管理是Linux核心的核心功能之一,負責

如何透過編寫程式碼來深入理解 PHP8 的運行機制 如何透過編寫程式碼來深入理解 PHP8 的運行機制 Sep 12, 2023 pm 02:25 PM

如何透過編寫程式碼來深入理解PHP8的運行機制引言:PHP8是PHP程式語言的下一個主要版本,帶來了許多新的功能和改進。要充分發揮PHP8的優勢,程式設計師需要對其運作機制有深入的理解。編寫程式碼的過程可以幫助我們更了解PHP8的內部運作原理。本文將介紹一些可以幫助我們深入理解PHP8運作機制的編碼技巧。一、了解PHP8的新特性在編寫程式碼之前,首先需要了解P

See all articles