首頁  >  文章  >  後端開發  >  PHP 效能分析與實驗:效能的宏觀分析

PHP 效能分析與實驗:效能的宏觀分析

WBOY
WBOY原創
2016-07-30 13:31:071362瀏覽

【編者按】此前,閱讀過了很多關於PHP 性能分析的文章,不過寫的都是一條一條的規則,而且,這些規則並沒有上下文,也沒有明確的實驗來體現出這些規則的優勢,同時討論的也著重於一些文法要點。本文就改變 PHP 效能分析的角度,並透過實例來分析出 PHP 的效能面向需要注意和改進的點。

PHP 性能分析与实验:性能的宏观分析

對PHP 表現的分析,我們從兩個層面著手,把這篇文章也分成了兩個部分,一個是宏觀層面,所謂宏觀層面,就是PHP 語言本身和環境層面,一個是應用層面,就是文法和使用規則的層面,不過不僅探討規則,更輔助以範例的分析。

宏觀層面,也就是對PHP 語言本身的效能分析又分為三個面向:

  1. PHP 作為解釋性語言表現有其天然的缺陷
  2. PHP 作為動態型別語言在表現上也有提升的空間
  3. 當下主流PHP 版本本身語言引擎性能

一、PHP 作為解釋性語言的性能分析與提升

PHP 作為一門腳本語言,也是解釋性語言,是其天然性能受限的原因,因為同編譯型語言在運行之前編譯成二進位程式碼不同,解釋性語言在每一次運行都面對原始腳本的輸入、解析、編譯,然後執行。如下是 PHP 作為解釋性語言的執行過程。

PHP 性能分析与实验:性能的宏观分析

如上所示,從上圖可以看到,每一次運行,都需要經歷三個解析、編譯、運行三個過程。

那優化的點在哪裡呢?可以想見,只要程式碼檔案確定,解析到編譯這一步都是確定的,因為檔案已不再變化,而執行,則由於輸入參數的不同而不同。在效能優化的世界裡,至上絕招就是在獲得相同結果的情況下,減少操作,這就是大名鼎鼎的快取。快取無所不在,快取也是效能優化的殺手鐧。於是乎 OpCode 快取這一招就出現了,只有第一次需要解析和編譯,而在後面的執行中,直接由腳本到 Opcode,從而實現了效能提速。執行流程如下圖所示:

PHP 性能分析与实验:性能的宏观分析

相對每一次解析、編譯,讀到腳本之後,直接從快取讀取字節碼的效率會有大幅度的提升,提升幅度到底有多大呢?

我們來做一個沒有 Opcode 快取的實驗。 20 個並發,總共 10000 次請求沒有經過 opcode 快取的請求,得到以下結果:

PHP 性能分析与实验:性能的宏观分析

其次,我們在伺服器上開啟 Opcode 快取。要實現 opcode 緩存,只需要安裝 APC、Zend OPCache、eAccelerator 擴充功能即可,即使安裝了多個,也只啟用其中一個。注意的是,修改了 php.ini 配置之後,需要重新載入 php-fpm 的配置。

這裡分別啟用 APC 和 Zend OPCache 做實驗。啟用 APC 的版本。

PHP 性能分析与实验:性能的宏观分析

可以看到,速度有了較大幅度的提升,原來每個請求 110ms,每秒處理請求 182 個,啟用了 APC 之後 68ms,每秒處理請求 294 個,提升速度將近 40%。

在啟用了 Zend Opcache 的版本中,得到同 APC 大致相當的結果。每秒處理請求 291 個,每個請求耗時 68.5ms。

PHP 性能分析与实验:性能的宏观分析

從上面的這個實驗可以看到,所用的測試頁面,有 40ms 以上的時間花在了語法解析和編譯這兩項上。透過將這兩個操作緩存,可以將這個處理過程的速度大大提升。

這裡附加補充一下,OpCode 到底是什麼東東,OpCode 編譯之後的字節碼,我們可以使用bytekit 這樣的工具,或者使用 vld PHP 擴充來實現對 PHP 的程式碼編譯。如下是 vld 插件解析程式碼的運行結果。

PHP 性能分析与实验:性能的宏观分析

可以看到每一行程式碼被編譯成對應的 OpCode 的輸出。

二、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 程式碼,我們嘗試編譯一下看看效果如何。 第一段程式碼如下:

PHP 性能分析与实验:性能的宏观分析

這是一段Swift 程式碼,字典只有14 個鍵值對,這段程式碼的編譯,9 分鐘了還沒編譯完成(5G 內存,2.4GHz CPU),編譯環境為Swift 1.2,Xcode 6.4。

PHP 性能分析与实验:性能的宏观分析

但是如果調整代碼如下:

PHP 性能分析与实验:性能的宏观分析

也就是加上了類型限定,避免了 planeLocation 的類型推斷。編譯過程花了 2S 。

PHP 性能分析与实验:性能的宏观分析

可見,作為動態類型附加的類型推斷操作大大降低了程式的編譯速度。 當然,這個例子有點極端,用 Swift 來類比 PHP 不一定合適,因為 Swift 語言本身也還在持續的演化過程中。本例子只是顯示在程式語言中,如果是動態類型語言,就涉及到對動態類型的處理,從編譯的角度講是會受影響的。

那麼作為動態型別的 PHP 的效率如何提升呢?從 PHP 語言本身這個層次是沒有辦法解決的,因為你怎麼寫也是動態型別的程式碼。解決方法就是將PHP轉化為靜態類型的表示,也就是做成擴展,可以看到,鳥哥的許多項目,例如 Yaf 框架,都是做成了擴展的,當然這也是由於鳥哥是 C 高手。擴充由於是 C 或 C++ 而寫,所以不再是動態類型,又加之是編譯好的,而 C 語言本身的效率也會提升很多。所以效率會大幅提高。

下面我們來看一段程式碼,這段程式碼,只是實作了簡單的質數運算,能計算指定值以內的質數個數,用的是普通的篩選法。現在看看擴充實現,跟 PHP 原生實現的效率差別,這個差別當然,不只是動態型別和編譯型別的差別,還有語言效率的差別。

首先是用純 PHP 寫成的演算法,計算 1,000 萬以內的質數個數,耗時在 33s 上下,實驗了三次,得到的結果基本上相同。

PHP 性能分析与实验:性能的宏观分析

其次,我們將這個求素數個數的過程,編寫成了 PHP 擴展,在擴展中實現了 getprimenumbers 函數,輸入一個整數,返回小於該整數的素數。得到的結果如下,這個效率的提升是非常驚人的,在 1.4s 上下即返回。速度提升 20 倍以上。

PHP 性能分析与实验:性能的宏观分析

可以想見,靜態和編譯類型的語言,其效率得到了驚人的提升。本程式的 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);
}

三、PHP 語言本身底層效能引擎提升

第三個效能最佳化層面是語言本身的效能提升,這就不是我們一般開發者所能做的了。在 PHP 7以前,寄望於小版本的改進,但是改進幅度不是非常的顯著,例如 PHP 5.3 、PHP 5.4、PHP 5.5、PHP 5.5 對同一段程式碼的效能比較,有一定程度的進步。

PHP 5.3 的版本在上面的例子中已講過,需要 33s 左右的時間,我們現在來看別的PHP版本。分別運作如下:

PHP 5.4 版,相較 5.3 版已經有一定程度的提升。快 6 秒左右。

PHP 性能分析与实验:性能的宏观分析

PHP 5.5 版在 PHP 5.4的基礎上又進了一步,快了 6S。

PHP 性能分析与实验:性能的宏观分析

PHP5.6 反而有些退步。

PHP 性能分析与实验:性能的宏观分析

PHP 7 果實真是效率提升驚人,是 PHP5.3 的 3 倍以上。

PHP 性能分析与实验:性能的宏观分析

以上是求素數腳本在各個PHP 版本之間的運行速度區別,儘管只測試了這一程序,也不是特別的嚴謹,但是這是在同一台機器上,而且編譯configure 參數也基本一樣,還是有一定可比性的。

在宏觀層面,除了上述的這些之外,在實際的部署過程中,對 PHP 效能的最佳化,也體現為減少在運作中所消耗的資源。所以 FastCGI 模式和 mod_php 的模式比傳統的 CGI 模式也更為受歡迎。因為在傳統的 CGI 模式中,在每個腳本運行都需要載入所有的模組。而在程式運行完成了之後,也要釋放模組資源。如下圖:

PHP 性能分析与实验:性能的宏观分析

而在 FastCGI 和 mod_php 模式中,則不需要如此。只有 php-fpm 或 Apache 啟動的時候,需要載入一次所有的模組,在具體的某次運行過程中,並不需要再次載入和釋放相關的模組資源。

PHP 性能分析与实验:性能的宏观分析

這樣程式效能的效率提升了。以上就是 PHP 宏觀層面的效能最佳化的分析,在本文的第二部分我們將探討應用方面的 PHP 最佳化準則。敬請期待!

以上就介紹了PHP 效能分析與實驗:效能的宏觀分析,包含了方面的內容,希望對PHP教學有興趣的朋友有所幫助。

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