為什麼PHP不適合計算密集型業務
回答這個問題,我們來了解一下為什麼說PHP慢?
PHP的慢是相對於C/C++級別的語言來說,事實上,PHP語言最初的設計,就不是用來解決計算密集型的應用場景。我們可以這樣粗略理解為,PHP為了提升開發效率,而犧牲了執行效率。
我們知道PHP一個很大的特點,就是弱型別特性,也就是說,我可以隨意定義一個變數,然後給它隨意賦值為各種型別的資料。以一個int整型數字為例子,在C語言中:
1 | intnum=200;// 通常是4位元組 |
但是,如果是PHP定義了一個同樣的變數,實際對應的儲存結構則是:
這個結構體將會佔據遠比C變數多得多的記憶體,PHP中定義方式如下:
1 | $a=200;//這變數將實際佔用對比C變數很多倍的儲存空間。 |
其實對PHP來說,無論儲存什麼型別的資料,都是用上述“通殺”的結構體實現。為了相容PHP程式設計師的變數型別“亂入”,PHP做到了對開發者的友好,但是對執行引擎很殘酷。單個變數記憶體消耗可能還不明顯,一旦用到PHP的陣列等,則複雜度指數上升(陣列的實現是HashTable)。然後,Zend引擎執行時,將這些PHP程式碼編譯為opcode(PHP的中間位元組碼,格式有點類似於彙編),由Zend引擎逐行解釋執行。
無論是字串的連線操作,還是陣列的簡單修改等,幾乎都是“PHP程式設計師一句話,Zend引擎跑斷腿”的節奏。因此,同樣的操作,對比C來說,PHP消耗了更多的CPU和記憶體等系統資源。除此之外,還有記憶體自動回收、變數型別判斷等等,都會增加系統資源的消耗。
例如,我用純PHP實現的快速排序函式和原生sort函式,排序10000個整型數字,來做一個耗時對比,結果如下:
原生的sort耗時3.44 ms,而我們自己實現的PHP函式sort則是68.79 ms。我們發現,兩者執行效率差距巨大。我的測試方式,是計算函式執行前後的時間間隔,而不是整個PHP指令碼從啟動到結束的時間。PHP指令碼啟動和關閉過程,本身有著一系列的初始化和清理工作,也會佔據不少的耗時。
通常情況下,PHP執行效率的排行是:
- 最快的是PHP語言結構(isset、echo等),PHP語言的一部分(它們根本不是函式)。
- 然後比較快的就是PHP的原生和拓展函式。PHP拓展,基於Zend API之上,用C實現的功能,執行效率和C /Java是屬於同一個數量級的。
- 真正慢的就是,我們通過PHP自己寫的程式碼和函式。例如,假如我們使用的比較重的純PHP實現的框架,因為框架本身的模組很多,所以,會明顯拖累語言層面的執行效率,同時佔據更多的記憶體。(國內的Yaf框架,以拓展的方式實現,因此執行效率遠快於純PHP寫的框架)
在一般情況下,我們並不推薦用過PHP實現邏輯複雜計算型別的功能,尤其是Web系統流量比較大的場景下。因此,PHP程式設計師應該對PHP的各種原生函式和各類拓展有一個比較廣泛的瞭解,在具體的功能實現場景中,尋求更原生的解決方案(原生介面或者拓展),而不是自己寫一堆複雜的PHP程式碼來實現這型別功能。
如果有足夠的PHP拓展開發實力,將這型別業務功能重寫為一個PHP拓展,也會大幅提升程式碼的執行效率。這是一個非常不錯的方式,也被廣泛應用PHP優化中。但是,自己編寫的PHP業務拓展的缺點也很明顯:
- 拓展開發耗時比較長,需求變更的時候修改也複雜,寫得不好可能會影響Web服務穩定性。(例如,在Apache的worker模式下,多執行緒場景下掛掉,會影響同一個程序下的其他正常子執行緒。如果是多執行緒的Web模式,編寫拓展還需要支援執行緒安全)
- 拓展在PHP版本升級的時候,可能需要做額外的相容工作。
- 人員變動後的維護和接手成本也比較高。
實際上,在網際網路一線企業中,更常見的解決方案,並非增加PHP拓展,而用C/C 獨立寫一個服務server,然後PHP通過socket和服務server通訊來完成業務處理,並不將PHP本身和業務耦合在一起。
不過,Web服務大部分的效能瓶頸都在網路傳輸和其他服務server的耗時上(例如MySQL等),PHP執行的耗時在整體耗時的佔用比例非常小,所以從業務角度來說,影響可能並不明顯。