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

2021-07-02 17:02:43 字數 2610 閱讀 3957

前言

在任何語言中,函式都是最基本的組成單元。對於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  common;

zend_op_array op_array;

zend_internal_function internal_function;

} zend_function;

typedef struct _zend_function_state zend_function_state;

其中type標明了函式的型別:使用者函式、內建函式、過載函式。common中包含函式的基本資訊,包括函式名,引數資訊,函式標誌(普通函式、靜態方法、抽象方法)等內容。另外,對於使用者函式,還有乙個函式符號表,記錄了內部變數等,這個將在後面詳述。 zend維護了乙個全域性function_table,這是乙個大的hahs表。函式呼叫的時候會首先根據函式名從表中找到對應的zend_function。當進行函式呼叫時候,虛擬機會根據type的不同決定呼叫方法, 不同型別的函式,其執行原理是不相同的 。

內建函式

內建函式,其本質上就是真正的c函式,每乙個內建函式,php在最終編譯後都會展開成為乙個名叫zif_***x的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函式的實現原理及效能分析(二)

類方法 類方法其執行原理和使用者函式是相同的,也是翻譯成opcodes順次呼叫。類的實現,zend用乙個資料結構zend class entry來實現,裡面儲存了類相關的一些基本資訊。這個entry是在php編譯的時候就已經處理完成。在 zend function的common中,有乙個成員叫做sc...

百度2013研發工程師A筆試

1 動態鏈結庫和靜態鏈結庫的優缺點 動態鏈結的優點 1 不占用源程式 段,節省空間。2 使程式更容易更新。3 減少物理頁面的切入。4 增加程式的可擴充套件性 動態鏈結的缺點 1 效能損失 2 相容性問題 靜態鏈結的優點 1 裝載速度快。2 不存在版本相容的問題 靜態鏈結的缺點 1 體積問題 2 可擴...

筆試收錄 百度 軟體工程師

1 請實現兩棵樹是否相等的比較,相等返回,否則返回其他值,並說明演算法複雜度。資料結構為 typedef struct treenodetreenode 函式介面為 int comptree treenode tree1,treenode tree2 注 a b兩棵樹相等當且僅當root c roo...