首頁  >  文章  >  後端開發  >  百度工程師講PHP函數的實現原理與性能分析(一)

百度工程師講PHP函數的實現原理與性能分析(一)

WBOY
WBOY原創
2016-08-08 09:24:01936瀏覽

百度工程師講PHP函數的實作原理及效能分析(一)

前言
在任何語言中,函數都是最基本的組成單元。對於php的函數,它有哪些特性?函數呼叫是怎麼實現的? php函數的效能如何,有什麼使用建議?本文將從原理出發進行分析結合實際的效能測試嘗試對這些問題進行回答,在了解實現的同時更好的編寫php程式。同時也會對一些常見的php函數進行介紹。
php函數的分類
在php中,橫向分割的話,函數分為兩大類: user function(內建函數) 和internal function(內建函數)。前者是使用者在程式中自訂的一些函數和方法,後者則是php本身提供的各類別函式庫函數(例如sprintf、array_push等)。使用者也可以透過擴展的方法來編寫庫函數,這個將在後面介紹。對於user function,又可以細分為function(函數)和method(類別方法),本文將就這三種函數分別進行分析和測試。
php函數的實作
一個php函數最後是如何執行,這個流程又是怎麼樣的呢?
要回答這個問題,我們先來看看php程式碼的執行所經過的流程。

從圖1可以看到,php實作了一個典型的動態語言執行過程:拿到一段程式碼後,經過詞法解析、語法解析等階段後,原始程式會被翻譯成一個指令(opcodes),然後ZEND虛擬機器順次執行這些指令完成操作。 Php本身是用c實現的,因此最終呼叫的也都是c的函數,實際上,我們可以把php看做是一個c開發的軟體。透過上面描述不難看出,php中函數的執行也是被翻譯成了opcodes來調用,每次函數調用實際上是執行了一條或多條指令。
對於每一個函數,zend都透過以下的資料結構來描述
typedef union _zend_function { zend_uchar type; /* MUST be the first element of this struct! */ struct { zend_uchar type; /* never used */ char *function_name; zend_class_entry *scope; zend_uint fn_flags; union _zend_function *prototype; zend_uint num_args; zend_uint required_num_args; zend_arg_info *arg_info; zend_bool pass_rest_by_reference; unsigned char return_reference; } common; zend_op_array op_array; zend_internal_function internal_function; } zend_function; typedef struct _zend_function_state { HashTable *function_symbol_table; zend_function *function; void *reserved[ZEND_MAX_RESERVED_RESOURCES]; } zend_function_state;
其中type標明了函數的類型:使用者函數、內建函數、重載函數。 Common包含函數的基本訊息,包括函數名,參數訊息,函數標誌(普通函數、靜態方法、抽象方法)等內容。另外,對於使用者函數,還有一個函數符號表,記錄了內部變數等,這個將在後面詳述。 Zend維護了一個全域function_table,這是一個大的hahs表。函數呼叫的時候會先根據函數名稱從表中找到對應的zend_function。當進行函數呼叫時候,虛擬機會根據type的不同決定呼叫方法, 不同類型的函數,其執行原理是不相同的 。
內建函數
內建函數,其本質上就是真正的c函數,每一個內建函數,php在最終編譯後都會展開成為一個名叫zif_xxxx的function,例如我們常見的sprintf,對應到底層就是zif_sprintf。 Zend在執行的時候,如果發現是內建函數,則只是簡單的做一個轉送操作。
Zend提供了一系列的api供調用,包括參數獲取、數組操作、記憶體分配等。內建函數的參數獲取,透過zend_parse_parameters方法來實現,對於數組、字串等參數,zend實現的是淺拷貝,因此這個效率是很高的。可以這樣說,對於php內建函數,其效率和對應c函數幾乎相同,唯一多了一次轉發呼叫。
內建函數在php中都是透過so的方式進行動態加載,使用者也可以根據需要自己編寫對應的so,也就是我們常說的擴充。 ZEND提供了一系列的api供擴展使用
用戶函數
和內建函數相比,用戶透過php實現的自訂函數具有完全不同的執行過程和實現原理。如前文所述,我們知道php程式碼是被翻譯成為了一條條opcode來執行的,使用者函數也不例外,實際中每個函數對應到一組opcode,這組指令被保存在zend_function中。於是,使用者函數的呼叫最終就是對應到一組opcodes的執行。
》》局部變數的保存及遞歸的實作
我們知道,函數遞歸是透過堆疊來完成的。在php中,也是利用類似的方法來實作。 Zend為每個php函數分配了一個活動符號表(active_sym_table),記錄目前函數中所有局部變數的狀態。所有的符號表透過堆疊的形式來維護,每當有函數呼叫的時候,就分配一個新的符號表併入棧。當呼叫結束後當前符號表出棧。由此實現了狀態的保存與遞歸。
對於棧的維護,zend在這裡做了最佳化。預先分配一個長度為N的靜態數組來模擬堆疊,這種透過靜態數組來模擬動態資料結構的手法在我們自己的程式中也經常有使用,這種方式避免了每次呼叫帶來的記憶體分配、銷毀。 ZEND只是在函數呼叫結束時將目前棧頂的符號表資料clean掉即可。因為靜態陣列長度為N,一旦函數呼叫層次超過N,程式不會出現堆疊溢出,這種情況下zend就會進行符號表的分配、銷毀,因此會導致效能下降很多。在zend裡面,N目前取值是32。因此,當我們寫php程式的時候,函數呼叫層次最好不要超過32。當然,如果是web應用,本身可以函數呼叫層次的深度。
》》參數的傳遞 和內建函數呼叫zend_parse_params來取得參數不同,使用者函數中參數的取得是透過指令來完成的。函數有幾個參數就對應幾條指令。具體到實作上就是普通的變數賦值。透過上面的分析可以看出,和內建函數相比,由於是自己維護堆疊表,而且每條指令的執行也是一個c函數,使用者函數的效能相對會差很多,後面會有具體的比較分析。因此,如果一個功能有對應php內建函數實現的盡量不要自己重新寫函數去實作。 

參考來源: 
百度工程師講PHP函數的實作原理與效能分析(一)
http://www.lai18.com/content/410091.html

以上就介紹了百度工程師講PHP函數的實現原理及效能分析(一),包括了方面的內容,希望對PHP教程有興趣的朋友有所幫助。

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