摘要:本文就改變性能分析的角度,并通過(guò)實(shí)例來(lái)分析出的性能方面需要注意和改進(jìn)的點(diǎn)。如下是作為解釋性語(yǔ)言的執(zhí)行過(guò)程。這里分別啟用和做實(shí)驗(yàn)。
【編者按】此前,閱讀過(guò)了很多關(guān)于 PHP 性能分析的文章,不過(guò)寫的都是一條一條的規(guī)則,而且,這些規(guī)則并沒有上下文,也沒有明確的實(shí)驗(yàn)來(lái)體現(xiàn)出這些規(guī)則的優(yōu)勢(shì),同時(shí)討論的也側(cè)重于一些語(yǔ)法要點(diǎn)。本文就改變 PHP 性能分析的角度,并通過(guò)實(shí)例來(lái)分析出 PHP 的性能方面需要注意和改進(jìn)的點(diǎn)。
對(duì) PHP 性能的分析,我們從兩個(gè)層面著手,把這篇文章也分成了兩個(gè)部分,一個(gè)是宏觀層面,所謂宏觀層面,就是 PHP 語(yǔ)言本身和環(huán)境層面,一個(gè)是應(yīng)用層面,就是語(yǔ)法和使用規(guī)則的層面,不過(guò)不僅探討規(guī)則,更輔助以示例的分析。
宏觀層面,也就是對(duì) PHP 語(yǔ)言本身的性能分析又分為三個(gè)方面:
PHP 作為解釋性語(yǔ)言性能有其天然的缺陷
PHP 作為動(dòng)態(tài)類型語(yǔ)言在性能上也有提升的空間
當(dāng)下主流 PHP 版本本身語(yǔ)言引擎性能
一、PHP 作為解釋性語(yǔ)言的性能分析與提升PHP 作為一門腳本語(yǔ)言,也是解釋性語(yǔ)言,是其天然性能受限的原因,因?yàn)橥幾g型語(yǔ)言在運(yùn)行之前編譯成二進(jìn)制代碼不同,解釋性語(yǔ)言在每一次運(yùn)行都面對(duì)原始腳本的輸入、解析、編譯,然后執(zhí)行。如下是 PHP 作為解釋性語(yǔ)言的執(zhí)行過(guò)程。
如上所示,從上圖可以看到,每一次運(yùn)行,都需要經(jīng)歷三個(gè)解析、編譯、運(yùn)行三個(gè)過(guò)程。
那優(yōu)化的點(diǎn)在哪里呢?可以想見,只要代碼文件確定,解析到編譯這一步都是確定的,因?yàn)槲募巡辉僮兓?,而?zhí)行,則由于輸入?yún)?shù)的不同而不同。在性能優(yōu)化的世界里,至上絕招就是在獲得同樣結(jié)果的情況下,減少操作,這就是大名鼎鼎的緩存。緩存無(wú)處不在,緩存也是性能優(yōu)化的殺手锏。于是乎 OpCode 緩存這一招就出現(xiàn)了,只有第一次需要解析和編譯,而在后面的執(zhí)行中,直接由腳本到 Opcode,從而實(shí)現(xiàn)了性能提速。執(zhí)行流程如下圖所示:
相對(duì)每一次解析、編譯,讀到腳本之后,直接從緩存讀取字節(jié)碼的效率會(huì)有大幅度的提升,提升幅度到底有多大呢?
我們來(lái)做一個(gè)沒有 Opcode 緩存的實(shí)驗(yàn)。20 個(gè)并發(fā),總共 10000 次請(qǐng)求沒有經(jīng)過(guò) opcode 緩存的請(qǐng)求,,得到如下結(jié)果:
其次,我們?cè)诜?wù)器上打開 Opcode 緩存。要想實(shí)現(xiàn) opcode 緩存,只需要安裝 APC、Zend OPCache、eAccelerator 擴(kuò)展即可,即使安裝了多個(gè),也只啟用其中一個(gè)。注意的是,修改了 php.ini 配置之后,需要重新加載 php-fpm 的配置。
這里分別啟用 APC 和 Zend OPCache 做實(shí)驗(yàn)。啟用 APC 的版本。
可以看到,速度有了較大幅度的提升,原來(lái)每個(gè)請(qǐng)求 110ms,每秒處理請(qǐng)求 182 個(gè),啟用了 APC 之后 68ms,每秒處理請(qǐng)求 294 個(gè),提升速度將近 40%。
在啟用了 Zend Opcache 的版本中,得到同 APC 大致相當(dāng)?shù)慕Y(jié)果。每秒處理請(qǐng)求 291 個(gè),每請(qǐng)求耗時(shí) 68.5ms。
從上面的這個(gè)實(shí)驗(yàn)可以看到,所用的測(cè)試頁(yè)面,有 40ms 以上的時(shí)間花在了語(yǔ)法解析和編譯這兩項(xiàng)上。通過(guò)將這兩個(gè)操作緩存,可以將這個(gè)處理過(guò)程的速度大大提升。
這里附加補(bǔ)充一下,OpCode 到底是什么東東,OpCode 編譯之后的字節(jié)碼,我們可以使用bytekit 這樣的工具,或者使用 vld PHP 擴(kuò)展來(lái)實(shí)現(xiàn)對(duì) PHP 的代碼編譯。如下是 vld 插件解析代碼的運(yùn)行結(jié)果。
可以看到每一行代碼被編譯成相應(yīng)的 OpCode 的輸出。
二、PHP 作為動(dòng)態(tài)類型語(yǔ)言的性能分析與改進(jìn)第二個(gè)是 PHP 語(yǔ)言是動(dòng)態(tài)類型的語(yǔ)言,動(dòng)態(tài)類型的語(yǔ)言本身由于涉及到在內(nèi)存中的類型推斷,比如在 PHP 中,兩個(gè)整數(shù)相加,我們能得到整數(shù)值,一個(gè)整數(shù)和一個(gè)字符串相加,甚至兩個(gè)字符串相加,都變成整數(shù)相加。而字符串和任何類型連接操作都成了字符串。
運(yùn)行結(jié)果如下:
float(40.11) int(40) int(30) int(30)語(yǔ)言的動(dòng)態(tài)類型為開發(fā)者提供了方便,語(yǔ)言本身則會(huì)因?yàn)閯?dòng)態(tài)類型而降低效率。在 Swift 中,有一個(gè)特性叫類型推斷,我們可以看看類型推斷會(huì)帶來(lái)多大的一個(gè)效率上的差別呢?對(duì)于需要類型推斷與不需要類型推斷兩段 Swift 代碼,我們嘗試編譯一下看看效果如何。
第一段代碼如下:這是一段 Swift 代碼,字典只有 14 個(gè)鍵值對(duì),這段代碼的編譯,9 分鐘了還沒有編譯完成(5G 內(nèi)存,2.4GHz CPU),編譯環(huán)境為 Swift 1.2,Xcode 6.4。
但是如果調(diào)整代碼如下:
也就是加上了類型限定,避免了 planeLocation 的類型推斷。編譯過(guò)程花了 2S 。
可見,作為動(dòng)態(tài)類型附加的類型推斷操作極大地降低了程序的編譯速度。
當(dāng)然,這個(gè)例子有點(diǎn)極端,用 Swift 來(lái)類比 PHP 也不一定合適,因?yàn)?Swift 語(yǔ)言本身也還在不斷的進(jìn)化過(guò)程中。本例子只是表明在編程語(yǔ)言中,如果是動(dòng)態(tài)類型語(yǔ)言,就涉及到對(duì)動(dòng)態(tài)類型的處理,從編譯的角度講是會(huì)受影響的。那么作為動(dòng)態(tài)類型的 PHP 的效率如何提升呢?從 PHP 語(yǔ)言本身這個(gè)層面是沒有辦法解決的,因?yàn)槟阍趺磳懸彩莿?dòng)態(tài)類型的代碼。解決辦法就是將PHP轉(zhuǎn)化為靜態(tài)類型的表示,也就是做成擴(kuò)展,可以看到,鳥哥的很多項(xiàng)目,比如 Yaf 框架,都是做成了擴(kuò)展的,當(dāng)然這也是由于鳥哥是 C 高手。擴(kuò)展由于是 C 或者 C++ 而寫,所以不再是動(dòng)態(tài)類型,又加之是編譯好的,而 C 語(yǔ)言本身的效率也會(huì)提升很多。所以效率會(huì)大幅度提高。
下面我們來(lái)看一段代碼,這段代碼,只是實(shí)現(xiàn)了簡(jiǎn)單的素?cái)?shù)運(yùn)算,能計(jì)算指定值以內(nèi)的素?cái)?shù)個(gè)數(shù),用的是普通的篩選法?,F(xiàn)在看看擴(kuò)展實(shí)現(xiàn),跟 PHP 原生實(shí)現(xiàn)的效率差別,這個(gè)差別當(dāng)然,不僅僅是動(dòng)態(tài)類型和編譯類型的差別,還有語(yǔ)言效率的差別。
首先是用純 PHP 寫成的算法,計(jì)算 1000 萬(wàn)以內(nèi)的素?cái)?shù)個(gè)數(shù),耗時(shí)在 33s 上下,實(shí)驗(yàn)了三次,得到的結(jié)果基本相同。
其次,我們將這個(gè)求素?cái)?shù)個(gè)數(shù)的過(guò)程,編寫成了 PHP 擴(kuò)展,在擴(kuò)展中實(shí)現(xiàn)了 get_prime_numbers 函數(shù),輸入一個(gè)整數(shù),返回小于該整數(shù)的素?cái)?shù)。得到的結(jié)果如下,這個(gè)效率的提升是非常驚人的,在 1.4s 上下即返回。速度提升 20 倍以上。
可以想見,靜態(tài)和編譯類型的語(yǔ)言,其效率得到了驚人的提升。本程序的 C 語(yǔ)言代碼如下:
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三、PHP 語(yǔ)言本身底層性能引擎提升f) { break; } } if(flag) { numbers[num] = i; num++; } } free(numbers); RETURN_LONG(num); } 第三個(gè)性能優(yōu)化層面是語(yǔ)言本身的性能提升,這個(gè)就不是我們普通開發(fā)者所能做的了。在 PHP 7以前,寄希望于小版本的改進(jìn),但是改進(jìn)幅度不是非常的顯著,比如 PHP 5.3 、PHP 5.4、PHP 5.5、PHP 5.5 對(duì)同一段代碼的性能比較,有一定程度的進(jìn)步。
PHP 5.3 的版本在上面的例子中已講過(guò),需要 33s 左右的時(shí)間,我們現(xiàn)在來(lái)看別的PHP版本。分別運(yùn)行如下:
PHP 5.4 版,相較 5.3 版已經(jīng)有一定程度的提升???6 秒左右。
PHP 5.5 版在 PHP 5.4的基礎(chǔ)上又進(jìn)了一步,快了 6S。
PHP5.6 反而有些退步。
PHP 7 果真是效率提升驚人,是 PHP5.3 的 3 倍以上。
以上是求素?cái)?shù)腳本在各個(gè) PHP 版本之間的運(yùn)行速度區(qū)別,盡管只測(cè)試了這一個(gè)程序,也不是特別的嚴(yán)謹(jǐn),但是這是在同一臺(tái)機(jī)器上,而且編譯 configure 參數(shù)也基本一樣,還是有一定可比性的。
在宏觀層面,除了上面的這些之外,在實(shí)際的部署過(guò)程中,對(duì) PHP 性能的優(yōu)化,還體現(xiàn)為要減少在運(yùn)行中所消耗的資源。所以 FastCGI 模式和 mod_php 的模式比傳統(tǒng)的 CGI 模式也更為受歡迎。因?yàn)樵趥鹘y(tǒng)的 CGI 模式中,在每一次腳本運(yùn)行都需要加載所有的模塊。而在程序運(yùn)行完成了之后,也要釋放模塊資源。如下圖所示:
而在 FastCGI 和 mod_php 模式中,則不需要如此。只有 php-fpm 或者 Apache 啟動(dòng)的時(shí)候,需要加載一次所有的模塊,在具體的某次運(yùn)行過(guò)程中,并不需要再次加載和釋放相關(guān)的模塊資源。
這樣程序性能的效率得到了提升。以上就是有關(guān) PHP 宏觀層面的性能優(yōu)化的分析,在本文的第二部分我們將探討應(yīng)用方面的 PHP 優(yōu)化準(zhǔn)則。敬請(qǐng)期待!
本文系 OneAPM 工程師編譯整理。想閱讀更多技術(shù)文章,請(qǐng)?jiān)L問(wèn) OneAPM 官方博客。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/21072.html
摘要:本文就改變性能分析的角度,并通過(guò)實(shí)例來(lái)分析出的性能方面需要注意和改進(jìn)的點(diǎn)。如下是作為解釋性語(yǔ)言的執(zhí)行過(guò)程。這里分別啟用和做實(shí)驗(yàn)。 編者按】此前,閱讀過(guò)了很多關(guān)于 PHP 性能分析的文章,不過(guò)寫的都是一條一條的規(guī)則,而且,這些規(guī)則并沒有上下文,也沒有明確的實(shí)驗(yàn)來(lái)體現(xiàn)出這些規(guī)則的優(yōu)勢(shì),同時(shí)討論的也側(cè)重于一些語(yǔ)法要點(diǎn)。本文就改變 PHP 性能分析的角度,并通過(guò)實(shí)例來(lái)分析出 PHP 的性能方面...
摘要:本文就改變性能分析的角度,并通過(guò)實(shí)例來(lái)分析出的性能方面需要注意和改進(jìn)的點(diǎn)。如下是作為解釋性語(yǔ)言的執(zhí)行過(guò)程。這里分別啟用和做實(shí)驗(yàn)。 此前,閱讀過(guò)了很多關(guān)于 PHP 性能分析的文章,不過(guò)寫的都是一條一條的規(guī)則,而且,這些規(guī)則并沒有上下文,也沒有明確的實(shí)驗(yàn)來(lái)體現(xiàn)出這些規(guī)則的優(yōu)勢(shì),同時(shí)討論的也側(cè)重于一些語(yǔ)法要點(diǎn)。本文就改變PHP 性能分析的角度,并通過(guò)實(shí)例來(lái)分析出 PHP 的性能方面需要注意和...
摘要:本文就改變性能分析的角度,并通過(guò)實(shí)例來(lái)分析出的性能方面需要注意和改進(jìn)的點(diǎn)。如下是作為解釋性語(yǔ)言的執(zhí)行過(guò)程。這里分別啟用和做實(shí)驗(yàn)。 此前,閱讀過(guò)了很多關(guān)于 PHP 性能分析的文章,不過(guò)寫的都是一條一條的規(guī)則,而且,這些規(guī)則并沒有上下文,也沒有明確的實(shí)驗(yàn)來(lái)體現(xiàn)出這些規(guī)則的優(yōu)勢(shì),同時(shí)討論的也側(cè)重于一些語(yǔ)法要點(diǎn)。本文就改變PHP 性能分析的角度,并通過(guò)實(shí)例來(lái)分析出 PHP 的性能方面需要注意和...
摘要:性能分析與實(shí)驗(yàn)性能的宏觀分析在上一篇文章中,我們從是解釋性語(yǔ)言動(dòng)態(tài)語(yǔ)言和底層實(shí)現(xiàn)等三個(gè)方面,探討了性能的問(wèn)題。在開始分析之前,我們得掌握一些與性能分析相關(guān)的函數(shù)。二性能分析則下面我們根據(jù)小程序來(lái)驗(yàn)證一些常見的性能差別。 【編者按】此前,閱讀過(guò)了很多關(guān)于 PHP 性能分析的文章,不過(guò)寫的都是一條一條的規(guī)則,而且,這些規(guī)則并沒有上下文,也沒有明確的實(shí)驗(yàn)來(lái)體現(xiàn)出這些規(guī)則的優(yōu)勢(shì),同時(shí)討論的也側(cè)...
摘要:而是說(shuō),程序的外部資源,往往是影響性能的重要因素,尤其是當(dāng)外部資源的連接和數(shù)據(jù)獲取本身速度達(dá)不到理想的結(jié)果時(shí)。 暫且不討論「PHP 是不是最好的編程語(yǔ)言」,本文我們將分別分析一下在 PHP 程序的后端外圍資源和前端外圍資源,它們對(duì)整個(gè) PHP Web 應(yīng)用體驗(yàn)的影響,這往往比語(yǔ)言本身大得多。 首先,后端外圍資源,是指跟 PHP 運(yùn)行過(guò)程中與語(yǔ)言本身無(wú)關(guān)的網(wǎng)絡(luò)與 IO 操作、存儲(chǔ)服務(wù)、中...
閱讀 2363·2021-11-24 09:39
閱讀 2581·2021-11-22 15:24
閱讀 3027·2021-09-02 09:48
閱讀 3075·2021-07-26 22:01
閱讀 1482·2019-08-30 11:09
閱讀 1723·2019-08-29 18:47
閱讀 651·2019-08-29 15:40
閱讀 2172·2019-08-29 15:22