- 相關(guān)推薦
探討PHP函數的實(shí)現原理及性能
前言
在任何語(yǔ)言中,函數都是最基本的組成單元。對于php的函數,它具有哪些特點(diǎn)?函數調用是怎么實(shí)現的?php函數的性能如何,有什么使用建議?本文將從原理出發(fā)進(jìn)行分析結合實(shí)際的性能測試嘗試對這些問(wèn)題進(jìn)行回答,在了解實(shí)現的同時(shí)更好的編寫(xiě)php程序。同時(shí)也會(huì )對一些常見(jiàn)的php函數進(jìn)行介紹。
php函數的分類(lèi)
在php中,橫向劃分的話(huà),函數分為兩大類(lèi): user function(內置函數) 和internal function(內置函數)。前者就是用戶(hù)在程序中自定義的一些函數和方法,后者則是php本身提供的各類(lèi)庫函數(比如sprintf、array_push等)。用戶(hù)也可以通過(guò)擴展的方法來(lái)編寫(xiě)庫函數,這個(gè)將在后面介紹。對于user function,又可以細分為function(函數)和method(類(lèi)方法),本文中將就這三種函數分別進(jìn)行分析和測試。
php函數的實(shí)現
一個(gè)php函數最終是如何執行,這個(gè)流程是怎么樣的呢?
要回答這個(gè)問(wèn)題,我們先來(lái)看看php代碼的執行所經(jīng)過(guò)的流程。
從圖1可以看到,php實(shí)現了一個(gè)典型的動(dòng)態(tài)語(yǔ)言執行過(guò)程:拿到一段代碼后,經(jīng)過(guò)詞法解析、語(yǔ)法解析等階段后,源程序會(huì )被翻譯成一個(gè)個(gè)指令(opcodes),然后ZEND虛擬機順次執行這些指令完成操作。Php本身是用c實(shí)現的,因此最終調用的也都是c的函數,實(shí)際上,我們可以把php看做是一個(gè)c開(kāi)發(fā)的軟件。通過(guò)上面描述不難看出,php中函數的執行也是被翻譯成了opcodes來(lái)調用,每次函數調用實(shí)際上是執行了一條或多條指令。
對于每一個(gè)函數,zend都通過(guò)以下的數據結構來(lái)描述
復制代碼 代碼如下:
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標明了函數的類(lèi)型:用戶(hù)函數、內置函數、重載函數。Common中包含函數的基本信息,包括函數名,參數信息,函數標志(普通函數、靜態(tài)方法、抽象方法)等內容。另外,對于用戶(hù)函數,還有一個(gè)函數符號表,記錄了內部變量等,這個(gè)將在后面詳述。 Zend維護了一個(gè)全局function_table,這是一個(gè)大的hahs表。函數調用的時(shí)候會(huì )首先根據函數名從表中找到對應的zend_function。當進(jìn)行函數調用時(shí)候,虛擬機會(huì )根據type的不同決定調用方法, 不同類(lèi)型的函數,其執行原理是不相同的 。
內置函數
內置函數,其本質(zhì)上就是真正的c函數,每一個(gè)內置函數,php在最終編譯后都會(huì )展開(kāi)成為一個(gè)名叫zif_xxxx的function,比如我們常見(jiàn)的sprintf,對應到底層就是zif_sprintf。Zend在執行的時(shí)候,如果發(fā)現是內置函數,則只是簡(jiǎn)單的做一個(gè)轉發(fā)操作。
Zend提供了一系列的api供調用,包括參數獲取、數組操作、內存分配等。內置函數的參數獲取,通過(guò)zend_parse_parameters方法來(lái)實(shí)現,對于數組、字符串等參數,zend實(shí)現的是淺拷貝,因此這個(gè)效率是很高的?梢赃@樣說(shuō),對于php內置函數,其效率和相應c函數幾乎相同,唯一多了一次轉發(fā)調用。
內置函數在php中都是通過(guò)so的方式進(jìn)行動(dòng)態(tài)加載,用戶(hù)也可以根據需要自己編寫(xiě)相應的so,也就是我們常說(shuō)的擴展。ZEND提供了一系列的api供擴展使用
用戶(hù)函數
和內置函數相比,用戶(hù)通過(guò)php實(shí)現的自定義函數具有完全不同的執行過(guò)程和實(shí)現原理。如前文所述,我們知道php代碼是被翻譯成為了一條條opcode來(lái)執行的,用戶(hù)函數也不例外,實(shí)際中每個(gè)函數對應到一組opcode,這組指令被保存在zend_function中。于是,用戶(hù)函數的調用最終就是對應到一組opcodes的執行。
局部變量的保存及遞歸的實(shí)現
我們知道,函數遞歸是通過(guò)堆棧來(lái)完成的。在php中,也是利用類(lèi)似的方法來(lái)實(shí)現。Zend為每個(gè)php函數分配了一個(gè)活動(dòng)符號表(active_sym_table),記錄當前函數中所有局部變量的狀態(tài)。所有的符號表通過(guò)堆棧的形式來(lái)維護,每當有函數調用的時(shí)候,分配一個(gè)新的符號表并入棧。當調用結束后當前符號表出棧。由此實(shí)現了狀態(tài)的保存和遞歸。
對于棧的維護,zend在這里做了優(yōu)化。預先分配一個(gè)長(cháng)度為N的靜態(tài)數組來(lái)模擬堆棧,這種通過(guò)靜態(tài)數組來(lái)模擬動(dòng)態(tài)數據結構的手法在我們自己的程序中也經(jīng)常有使用,這種方式避免了每次調用帶來(lái)的內存分配、銷(xiāo)毀。ZEND只是在函數調用結束時(shí)將當前棧頂的符號表數據clean掉即可。因為靜態(tài)數組長(cháng)度為N,一旦函數調用層次超過(guò)N,程序不會(huì )出現棧溢出,這種情況下zend就會(huì )進(jìn)行符號表的分配、銷(xiāo)毀,因此會(huì )導致性能下降很多。在zend里面,N目前取值是32。因此,我們編寫(xiě)php程序的時(shí)候,函數調用層次最好不要超過(guò)32。當然,如果是web應用,本身可以函數調用層次的深度。
參數的傳遞 和內置函數調用zend_parse_params來(lái)獲取參數不同,用戶(hù)函數中參數的獲取是通過(guò)指令來(lái)完成的。函數有幾個(gè)參數就對應幾條指令。具體到實(shí)現上就是普通的變量賦值。通過(guò)上面的分析可以看出,和內置函數相比,由于是自己維護堆棧表,而且每條指令的執行也是一個(gè)c函數,用戶(hù)函數的性能相對會(huì )差很多,后面會(huì )有具體的對比分析。因此,如果一個(gè)功能有對應php內置函數實(shí)現的盡量不要自己重新寫(xiě)函數去實(shí)現。
【探討PHP函數的實(shí)現原理及性能】相關(guān)文章:
關(guān)于php堆排序實(shí)現原理與應用方法04-01
PHP的壓縮函數03-31
淺析php函數的實(shí)例04-01
php中session的實(shí)現原理以及大網(wǎng)站應用應注意的問(wèn)題分析04-01
簡(jiǎn)單介紹php構造函數用法03-15
PHP中的排序函數區別分析03-31
PHP中函數的使用說(shuō)明03-30
php外部執行命令函數03-31