【編者按】此前,閱讀過了不少關於 PHP 性能分析的文章,不過寫的都是一條一條的規則,並且,這些規則並無上下文,也沒有明確的實驗來體現出這些規則的優點,同時討論的也側重於一些語法要點。本文就改變 PHP 性能分析的角度,並經過實例來分析出 PHP 的性能方面須要注意和改進的點。php
對 PHP 性能的分析,咱們從兩個層面着手,把這篇文章也分紅了兩個部分,一個是宏觀層面,所謂宏觀層面,就是 PHP 語言自己和環境層面,一個是應用層面,就是語法和使用規則的層面,不過不只探討規則,更輔助以示例的分析。html
宏觀層面,也就是對 PHP 語言自己的性能分析又分爲三個方面:算法
##1、PHP 做爲解釋性語言的性能分析與提高編程
PHP 做爲一門腳本語言,也是解釋性語言,是其自然性能受限的緣由,由於同編譯型語言在運行以前編譯成二進制代碼不一樣,解釋性語言在每一次運行都面對原始腳本的輸入、解析、編譯,而後執行。以下是 PHP 做爲解釋性語言的執行過程。緩存
如上所示,從上圖能夠看到,每一次運行,都須要經歷三個解析、編譯、運行三個過程。性能優化
那優化的點在哪裏呢?能夠想見,只要代碼文件肯定,解析到編譯這一步都是肯定的,由於文件已再也不變化,而執行,則因爲輸入參數的不一樣而不一樣。在性能優化的世界裏,至上絕招就是在得到一樣結果的狀況下,減小操做,這就是大名鼎鼎的緩存。緩存無處不在,緩存也是性能優化的殺手鐗。因而乎 OpCode 緩存這一招就出現了,只有第一次須要解析和編譯,而在後面的執行中,直接由腳本到 Opcode,從而實現了性能提速。執行流程以下圖所示:服務器
相對每一次解析、編譯,讀到腳本以後,直接從緩存讀取字節碼的效率會有大幅度的提高,提高幅度到底有多大呢?併發
咱們來作一個沒有 Opcode 緩存的實驗。20 個併發,總共 10000 次請求沒有通過 opcode 緩存的請求,,獲得以下結果:框架
其次,咱們在服務器上打開 Opcode 緩存。要想實現 opcode 緩存,只須要安裝 APC、Zend OPCache、eAccelerator 擴展便可,即便安裝了多個,也只啓用其中一個。注意的是,修改了 php.ini 配置以後,須要從新加載 php-fpm 的配置。編程語言
這裏分別啓用 APC 和 Zend OPCache 作實驗。啓用 APC 的版本。
能夠看到,速度有了較大幅度的提高,原來每一個請求 110ms,每秒處理請求 182 個,啓用了 APC 以後 68ms,每秒處理請求 294 個,提高速度將近 40%。
在啓用了 Zend Opcache 的版本中,獲得同 APC 大體至關的結果。每秒處理請求 291 個,每請求耗時 68.5ms。
從上面的這個實驗能夠看到,所用的測試頁面,有 40ms 以上的時間花在了語法解析和編譯這兩項上。經過將這兩個操做緩存,能夠將這個處理過程的速度大大提高。
這裏附加補充一下,OpCode 究竟是什麼東東,OpCode 編譯以後的字節碼,咱們可使用bytekit 這樣的工具,或者使用 vld PHP 擴展來實現對 PHP 的代碼編譯。以下是 vld 插件解析代碼的運行結果。
能夠看到每一行代碼被編譯成相應的 OpCode 的輸出。
##2、PHP 做爲動態類型語言的性能分析與改進
第二個是 PHP 語言是動態類型的語言,動態類型的語言自己因爲涉及到在內存中的類型推斷,好比在 PHP 中,兩個整數相加,咱們能獲得整數值,一個整數和一個字符串相加,甚至兩個字符串相加,都變成整數相加。而字符串和任何類型鏈接操做都成了字符串。
<?php $a = 10.11; $b = "30"; var_dump($a+$b); var_dump("10"+$b); var_dump(10+"20"); var_dump("10"+"20");
運行結果以下:
float(40.11) int(40) int(30) int(30)
語言的動態類型爲開發者提供了方便,語言自己則會由於動態類型而下降效率。在 Swift 中,有一個特性叫類型推斷,咱們能夠看看類型推斷會帶來多大的一個效率上的差異呢?對於須要類型推斷與不須要類型推斷兩段 Swift 代碼,咱們嘗試編譯一下看看效果如何。 第一段代碼以下:
這是一段 Swift 代碼,字典只有 14 個鍵值對,這段代碼的編譯,9 分鐘了尚未編譯完成(5G 內存,2.4GHz CPU),編譯環境爲 Swift 1.2,Xcode 6.4。
可是若是調整代碼以下:
也就是加上了類型限定,避免了 planeLocation 的類型推斷。編譯過程花了 2S 。
可見,做爲動態類型附加的類型推斷操做極大地下降了程序的編譯速度。 固然,這個例子有點極端,用 Swift 來類比 PHP 也不必定合適,由於 Swift 語言自己也還在不斷的進化過程當中。本例子只是代表在編程語言中,若是是動態類型語言,就涉及到對動態類型的處理,從編譯的角度講是會受影響的。
那麼做爲動態類型的 PHP 的效率如何提高呢?從 PHP 語言自己這個層面是沒有辦法解決的,由於你怎麼寫也是動態類型的代碼。解決辦法就是將PHP轉化爲靜態類型的表示,也就是作成擴展,能夠看到,鳥哥的不少項目,好比 Yaf 框架,都是作成了擴展的,固然這也是因爲鳥哥是 C 高手。擴展因爲是 C 或者 C++ 而寫,因此再也不是動態類型,又加之是編譯好的,而 C 語言自己的效率也會提高不少。因此效率會大幅度提升。
下面咱們來看一段代碼,這段代碼,只是實現了簡單的素數運算,能計算指定值之內的素數個數,用的是普通的篩選法。如今看看擴展實現,跟 PHP 原生實現的效率差異,這個差異固然,不只僅是動態類型和編譯類型的差異,還有語言效率的差異。
首先是用純 PHP 寫成的算法,計算 1000 萬之內的素數個數,耗時在 33s 上下,實驗了三次,獲得的結果基本相同。
其次,咱們將這個求素數個數的過程,編寫成了 PHP 擴展,在擴展中實現了 get_prime_numbers 函數,輸入一個整數,返回小於該整數的素數。獲得的結果以下,這個效率的提高是很是驚人的,在 1.4s 上下即返回。速度提高 20 倍以上。
能夠想見,靜態和編譯類型的語言,其效率獲得了驚人的提高。本程序的 C 語言代碼以下:
PHP_FUNCTION(get_prime_numbers) { long value; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &value) == FAILURE) { return; } int *numbers = (int *)malloc(sizeof(int)*128*10000); memset(numbers, 0x0, 128*10000); int num = 2; numbers[0] = 2; numbers[1] = 3; bool flag = true; double f = 0; int i = 0; int j = 0; for(i=5; i<=value; i+=2) { flag = true; f = sqrt(i); for(j=0; j<num;j++) { if(i%numbers[j]==0) { flag = false; break; } if(numbers[j]>f) { break; } } if(flag) { numbers[num] = i; num++; } } free(numbers); RETURN_LONG(num); }
##3、PHP 語言自己底層性能引擎提高
第三個性能優化層面是語言自己的性能提高,這個就不是咱們普通開發者所能作的了。在 PHP 7之前,寄但願於小版本的改進,可是改進幅度不是很是的顯著,好比 PHP 5.3 、PHP 5.四、PHP 5.五、PHP 5.5 對同一段代碼的性能比較,有必定程度的進步。
PHP 5.3 的版本在上面的例子中已講過,須要 33s 左右的時間,咱們如今來看別的PHP版本。分別運行以下:
PHP 5.4 版,相較 5.3 版已經有必定程度的提高。快 6 秒左右。
PHP 5.5 版在 PHP 5.4的基礎上又進了一步,快了 6S。
PHP5.6 反而有些退步。
PHP 7 果然是效率提高驚人,是 PHP5.3 的 3 倍以上。
以上是求素數腳本在各個 PHP 版本之間的運行速度區別,儘管只測試了這一個程序,也不是特別的嚴謹,可是這是在同一臺機器上,並且編譯 configure 參數也基本同樣,仍是有必定可比性的。
在宏觀層面,除了上面的這些以外,在實際的部署過程當中,對 PHP 性能的優化,還體現爲要減小在運行中所消耗的資源。因此 FastCGI 模式和 mod_php 的模式比傳統的 CGI 模式也更爲受歡迎。由於在傳統的 CGI 模式中,在每一次腳本運行都須要加載全部的模塊。而在程序運行完成了以後,也要釋放模塊資源。以下圖所示:
而在 FastCGI 和 mod_php 模式中,則不須要如此。只有 php-fpm 或者 Apache 啓動的時候,須要加載一次全部的模塊,在具體的某次運行過程當中,並不須要再次加載和釋放相關的模塊資源。
這樣程序性能的效率獲得了提高。以上就是有關 PHP 宏觀層面的性能優化的分析,在本文的第二部分咱們將探討應用方面的 PHP 優化準則。敬請期待!
本文系 OneAPM 工程師編譯整理。OneAPM 是應用性能管理領域的新興領軍企業,能幫助企業用戶和開發者輕鬆實現:緩慢的程序代碼和 SQL 語句的實時抓取。想閱讀更多技術文章,請訪問 OneAPM 官方博客。