PHP執行原理php
php是一門應用很是簡單,開發效率極高的一門語言,其弱類型的變量能省去程序員大量的定義變量、類型轉換等的時間和精力。它是一種適用於web開發的動態語言。html
多進程模型:這樣能作到進程間互相不受影響,對於進程的資源利用更快速、便捷java
弱類型語言:和強類型語言C、C++、java等語言不一樣,php中變量的類型並非一開始就肯定的,他是在運行時才肯定的,能夠隱式或顯式的對其進行類型轉換,這就使其在開發中很是的靈活,程序員無需關注變量類型的問題c++
Zend引擎+ 組件(ext)的模式下降內部的耦合程序員
中間層(sapi)隔絕web server 和phpweb
語法簡單靈活,規範少。這一點就有利有弊了。。。apache
php從上到下一共四層體系:編程
Zend引擎:Zend總體用C實現,是php的內核部分,它將php的代碼翻譯成可執行的opcode的,處理並實現相應的處理方法(原理:鳥哥的博客)、實現了基本的數據結構、內存分配及管理、提供了相應api方法供外部使用,是一切的核心。api
Extensions:圍繞着Zend引擎,extensions經過組件的方式提供各類基礎服務,經常使用的內置函數array、標準庫等都是經過extension來實現的,用戶也能夠根據須要實現本身的extension以達到功能擴展等目的如貼吧正在使用的 PHP中間層、富文本解析就是extension的典型應用)。數組
Sapi:Sapi全稱Server Application Programing Interface, 也就是服務端應用編程接口,Sapi經過一些列鉤子函數,使php能夠和外圍交互數據,這是PHP很是優雅和成功的一個設計,經過 sapi成功的將PHP自己和上層應用解耦隔離,PHP能夠再也不考慮如何針對不一樣應用進行兼容,而應用自己也能夠針對本身的特色實現不一樣的處理方式。
上層應用:這就是程序員編寫的應用程序,經過不一樣的sapi方式獲得各類各樣的應用模式,如經過webserver實現web應用,在命令行下以腳本的方式運行等等
如前所屬,Sapi經過一些列的接口,使外部應用能夠和php交換數據並能夠根據不一樣的應用特色實現特定的處理方法,常見的sapi有:
apache2handler:以apache做爲webserver,採用MOD_PHP模式運行時候的處理方式,也是如今應用最普遍的一種
cgi:這是webserver和php的另一種交互方式,也就是fastcgi協議
cli:命令調試應用模式
從圖中能夠看出,php經過Zend引擎實現了一個典型的動態語言的執行過程:獲取一段代碼片斷,通過詞法解析、語法解析等階段,源程序被翻譯成一個個指令(opcodes),而後Zend虛擬機順序執行這些指令。PHP自己是用C語言實現的,所以最終調用的也是C語言的函數。
PHP的執行的核心是翻譯出來的一條一條指令,也即opcode
Opcode是PHP程序執行的最基本單位。一個opcode由兩個參數(op1,op2)、返回值和處理函數組成。PHP程序最終被翻譯爲一組opcode處理函數的順序執行。
經常使用的幾個函數:
END_ASSIGN_SPEC_CV_CV_HANDLER : 變量分配 ( a=b)
ZEND_DO_FCALL_BY_NAME_SPEC_HANDLER:函數調用
ZEND_CONCAT_SPEC_CV_CV_HANDLER:字符串拼接 a.b
ZEND_ADD_SPEC_CV_CONST_HANDLER: 加法運算a+2
ZEND_IS_EQUAL_SPEC_CV_CONST:判斷相等 a==1
ZEND_IS_IDENTICAL_SPEC_CV_CONST:判斷相等 a===1
Zend引擎做爲php的內核,主要的設計機制有:
HashTable是Zend的核心數據結構,在php裏面幾乎用來實現全部功能,php的數據array()就是典型的應用。此外在Zend內部,如函數符號表、全景變量都是經過HashTable來實現的。
Zend hash table 實現了典型的hash表散列結構,同時經過附加一個雙向鏈表,提供了正向、反向、遍歷數組的功能,結構如圖:
能夠看到,在hash table中既有key->value形式的散列結構,也有雙向鏈表模式,使得它可以很是方便的支持快速查找和線性遍歷。
散列結構:Zend的散列結構是典型的hash表模型,經過鏈表的方式來解決衝突。須要注意的是zend的hash table是一個自增加的數據結構,當hash表數目滿了以後,其自己會動態以2倍的方式擴容並從新元素位置。初始大小均爲8。另外,在進行 key->value快速查找時候,zend自己還作了一些優化,經過空間換時間的方式加快速度。好比在每一個元素中都會用一個變量 nKeyLength標識key的長度以做快速斷定。
雙向鏈表:Zend hash table經過一個鏈表結構,實現了元素的線性遍歷。理論上,作遍歷使用單向鏈表就夠了,之因此使用雙向鏈表,主要目的是爲了快速刪除,避免遍歷。 Zend hash table是一種複合型的結構,做爲數組使用時,即支持常見的關聯數組也可以做爲順序索引數字來使用,甚至容許2者的混合。
PHP關聯數組:關聯數組是典型的hash_table應用。一次查詢過程通過以下幾步(從代碼能夠看出,這是一個常見的hash查詢過程並增長一些快速斷定加速查找):
01 getKeyHashValue h; 02 index = n & nTableMask; 03 Bucket *p = arBucket[index]; 04 while (p) { 05 if ((p->h == h) && (p->nKeyLength == nKeyLength)) { 06 RETURN p->data; 07 } 08 p=p->next; 09 } 10 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處理
5.2 PHP變量的實現原理
PHP是一門弱類型語言,不嚴格區分變量的類型。PHP的變量能夠分爲簡單類型(int、sting、bool)、集合類型(array, resource, object) 和常量(const),全部的變量在底層都因此同一種結構zval
zval是zend中很是重要的數據結構,用來標示並實現php的變量,其數據結構以下:
struct _zval_struct { zvalue_value value; /* value */ zend_uint refcount__gc; /* variable ref count */ zend_uchar type; /* active type */ zend_uchar is_ref__gc; /* if it is a ref variable */ }; typedef struct _zval_struct zval;
其中,
zval_value value是變量的實際值,具體來講是一個zvalue_value聯合體:
typedef union _zvalue_value { long lval; /* long value */ double dval; /* double value */ struct { /* string */ char *val; int len; } str; HashTable *ht; /* hash table value,used for array */ zend_object_value obj; /* object */ } zvalue_value;
zend_uint refcount__gc是一個計數器,用來保存多少變量(或者符號,symbols )指向了該zval。在變量生成時,其refcount=1,典型的賦值操做如$a = $b會令zval的refcount加1,而unset操做會相應的減1。在PHP5.3以前,使用引用計數的機制來實現GC,若是一個zval的refcount較少到0,那麼Zend引擎會認爲沒有任何變量指向該zval,所以會釋放該zval所佔的內存空間。但,事情有時並不會那麼簡單。後面咱們會看到,單純的引用計數機制沒法GC掉循環引用的zval,即便指向該zval的變量已經被unset,從而致使了內存泄露(Memory Leak)。
zend_uchar type該字段用於代表變量的實際類型。PHP中的變量包括四種標量類型(bool,int,float,string),兩種複合類型(array, object)和兩種特殊的類型(resource 和NULL)。在zend內部,這些類型對應於下面的宏(代碼位置 phpsrc/Zend/zend.h)
#define IS_NULL 0 #define IS_LONG 1 #define IS_DOUBLE 2 #define IS_BOOL 3 #define IS_ARRAY 4 #define IS_OBJECT 5 #define IS_STRING 6 #define IS_RESOURCE 7 #define IS_CONSTANT 8 #define IS_CONSTANT_ARRAY 9 #define IS_CALLABLE 10
is_ref__gc這個字段用於標記變量是不是引用變量。對於普通的變量,該值爲0,而對於引用型的變量,該值爲1。這個變量會影響zval的共享、分離等
整數、浮點數是PHP中的基礎類型之一,也是一個簡單型變量。對於整數和浮點數,在zvalue中直接存儲對應的值。其類型分別是long和double。
從zvalue結構中能夠看出,對於整數類型,和c等強類型語言不一樣,PHP是不區分int、unsigned int、long、long long等類型的,對它來講,整數只有一種類型也就是long。由此,能夠看出,在PHP裏面,整數的取值範圍是由編譯器位數來決定而不是固定不變的。在php中若是整數越界了會發生什麼?php會自動給整數轉換成浮點數類型
對於浮點數,相似整數,它也不區分float和double而是統一隻有double一種類型
和整數同樣,字符變量也是PHP中的基礎類型和簡單型變量。經過zvalue結構能夠看出,在PHP中,字符串是由由指向實際數據的指針和長度結 構體組成,這點和c++中的string比較相似。因爲經過一個實際變量表示長度,和c不一樣,它的字符串能夠是2進制數據(包含0),同時在PHP中, 求字符串長度strlen是O(1)操做
常見的字符串拼接方式及速度比較:
假設有以下4個變量:strA=‘123’; strB = ‘456’; intA=123; intB=456;
如今對以下的幾種字符串拼接方式作一個比較和說明:
1 res = strA.strB和res = 「strAstrB」
這種狀況下,zend會從新malloc一塊內存並進行相應處理,其速度通常。
2 strA = strA.strB
這種是速度最快的,zend會在當前strA基礎上直接relloc,避免重複拷貝
3 res = intA.intB
這種速度較慢,由於須要作隱式的格式轉換,實際編寫程序中也應該注意儘可能避免
4 strA = sprintf (「%s%s」,strA,strB);
這會是最慢的一種方式,由於sprintf在PHP中並非一個語言結構,自己對於格式識別和處理就須要耗費比較多時間,另外自己機制也是malloc。不過sprintf的方式最具可讀性,實際中能夠根據具體狀況靈活選擇。
PHP的數組是經過Zend Hash Table來自然實現。
foreach操做如何實現?對一個數組的foreach就是經過遍歷hashtable中的雙向鏈表完成。對於索引數組,經過foreach遍 歷效率比for高不少,省去了key->value的查找。count操做直接調用 HashTable->NumOfElements,O(1)操做。對於’123’這樣的字符串,zend會轉換爲其整數形 式。arr[‘123’]和arr[123]
引用計數在內存回收、字符串操做等地方使用很是普遍。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中的局部變量和全局變量是如何實現的?對於一個請求,任意時刻PHP均可以看到兩個符號表(symbol_table和 active_symbol_table),其中前者用來維護全局變量。後者是一個指針,指向當前活動的變量符號表,當程序進入到某個函數中時,zend 就會爲它分配一個符號表x同時將active_symbol_table指向a。經過這樣的方式實現全局、局部變量的區分。
獲取變量值:PHP的符號表是經過hash_table實現的,對於每一個變量都分配惟一標識,獲取的時候根據標識從表中找到相應zval返回。
函數中使用全局變量:在函數中,咱們能夠經過顯式申明global來使用全局變量。在active_symbol_table中建立symbol_table中同名變量的引用(引用變量的值要更新你們會一塊兒更新),若是symbol_table中沒有同名變量則會先建立。
參考: