首頁 後端開發 php教程 PHP7更新及效能最佳化的介紹(圖文)

PHP7更新及效能最佳化的介紹(圖文)

Feb 18, 2019 pm 02:35 PM
php7

這篇文章帶給大家的內容是關於PHP7更新及效能優化的介紹(圖文),有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。

PHP7革新與性能優化

有幸參與2015年的PHP技術高峰會(PHPCON),聽了鳥哥(惠新宸)的關於PHP7的新特性和效能優化的分享,一切都令人感到興奮。鳥哥是國內最權威的PHP專家,他的分享有很多非常有價值的東西,我透過整理分享的PPT和收集相關資料,整理為這篇解讀性質的技術文章,希望能給做PHP開發的同學一些幫助。

PHP已經走過了20年的歷史,直到今天,PHP7都發布了RC版,據說,PHP7正式版應該會在2015年11月份左右發布。 PHP7對於上一個系列的PHP5.*,可以說是一個大規模的革新,尤其是在性能方面實現跨越式的大幅提升。

PHP是一種在全球廣泛使用的Web開發語言,PHP7的革新也當然會為這些Web服務帶來更深刻的改變。這裡引用鳥哥PPT中的一個圖表(82%的Web網站有使用PHP作為開發語言):

 PHP7革新与性能优化 - 徐汉彬Hansion - 技术行者

(註:一個web網站可以會使用多種語言作為它的開發語言)

(註:本文含有不少從鳥哥PPT裡的截圖,圖片版權歸鳥哥所有)

我們先看看兩張激動人心的性能測試結果圖:

Benchmark比較(圖片來自於PPT):

 PHP7革新与性能优化 - 徐汉彬Hansion - 技术行者

PHP7的效能測試結果,效能壓測結果,耗時從2.991下降到1.186,大幅度下降60%。

WordPress的QPS壓測(圖片來自於PPT):

 PHP7革新与性能优化 - 徐汉彬Hansion - 技术行者

而在WordPress專案中,PHP7對比PHP5.6,QPS提升2.77倍。

看完令人興奮的效能測試結果對比,我們就進入正題哈。 PHP7的新增特性很多,不過,我們會更聚焦在那些主要的變化。

一、新增功能與改變

1. 標量類型與回傳類型宣告(Scalar Type Declarations & Scalar Type Declarations)

PHP語言一個非常重要的特點是“弱類型”,它讓PHP的程式變得非常容易編寫,新手接觸PHP能夠快速上手,不過,它也伴隨著一些爭議。支持變數類型的定義,可以說是革新性質的變化,PHP開始以可選的方式支援類型定義。除此之外,還引入了一個開關指令declare(strict_type=1);,當這個指令一旦開啟,將會強制當前文件下的程序遵循嚴格的函數傳參類型和返回類型。

例如一個add函數加上類型定義,可以寫成這樣:

 PHP7革新与性能优化 - 徐汉彬Hansion - 技术行者

如果配合強制類型開關指令,則可以變成這樣:

 PHP7革新与性能优化 - 徐汉彬Hansion - 技术行者

如果不開啟strict_type,PHP將會嘗試幫你轉換成要求的類型,而開啟之後,會改變PHP就不再做類型轉換,類型不匹配就會拋出錯誤。對於喜歡「強型別」語言的同學來說,這是一大福音。

更詳細的介紹:

PHP7標量類型宣告RFC[翻譯]  

2. 更多的Error變成可擷取的Exception

PHP7實作了一個全域的throwable接口,原來的Exception和部分Error都實作了這個介面(interface), 以介面的方式定義了異常的繼承結構。於是,PHP7中更多的Error變為可捕獲的Exception返回給開發者,如果不進行捕獲則為Error,如果捕獲就變為一個可在程序內處理的Exception。這些可被捕獲的Error通常都是不會對程式造成致命傷害的Error,例如函數不存。 PHP7進一步方便開發者處理,讓開發者對程式的掌控能力更強。因為在預設情況下,Error會直接導致程式中斷,而PHP7則提供捕捉並且處理的能力,讓程式繼續執行下去,為程式設計師提供更靈活的選擇。

例如,執行一個我們不確定是否存在的函數,PHP5相容的做法是在函數被呼叫之前追加的判斷function_exist,而PHP7則支援捕獲Exception的處理方式。

如下圖的範例(截圖來自PPT內):

 PHP7革新与性能优化 - 徐汉彬Hansion - 技术行者

3. AST(Abstract Syntax Tree,抽象語法樹)

AST在PHP編譯過程中作為一個中間件的角色,替換原來直接從解釋器吐出opcode的方式,讓解釋器(parser)和編譯器(compliler)解耦,可以減少一些Hack程式碼,同時,讓實作更容易理解和可維護。

PHP5:

 PHP7革新与性能优化 - 徐汉彬Hansion - 技术行者 

PHP7:

 PHP7革新与性能优化 - 徐汉彬Hansion - 技术行者

更多AST資訊:

https://wiki.php.net/rfc/abstract_syntax_tree

4. Native TLS(Native Thread local storage,原生執行緒本地儲存)

PHP在多執行緒模式下(例如, Web伺服器Apache的woker和event模式,就是多執行緒),需要解決「線程安全」(TS,Thread Safe)的問題,因為執行緒是共享進程的記憶體空間的,所以每個執行緒本身需要透過某種方式,建構私有的空間來保存自己的私有數據,避免和其他執行緒相互污染。而PHP5採用的方式,就是維護一個全域大數組,為每個執行緒分配一份獨立的儲存空間,而執行緒透過各自擁有的key值來存取這個全域資料組。

而這個獨特的key值在PHP5中需要傳遞給每一個需要用到全域變數的函數,PHP7認為這種傳遞的方式並不友好,並且存在一些問題。因而,嘗試採用一個全域的執行緒特定變數來保存這個key值。

相關的Native TLS問題:

https://wiki.php.net/rfc/native-tls

5. 其他新功能

# PHP7新功能和變化不少,我們這裡並不全部展開來細說哈。

(1) Int64支持,統一不同平台下的整數長度,字串和檔案上傳都支援大於2GB。

(2) 統一變數語法(Uniform variable syntax)。

(3) foreach表現行為一致(Consistently foreach behaviors)

(4) 新的操作符 <=>, ??

#(5) Unicode字元格式支援(\u{xxxxx})

(6) 匿名類別支援(Anonymous Class)

… …

二、跨越式的效能突破:全速前進

1. JIT與效能

Just In Time(即時編譯)是一種軟體最佳化技術,指在運作時才會去編譯字節為機器碼。從直覺出發,我們都很容易認為,機器碼是電腦能夠直接辨識和執行的,比起Zend讀取opcode逐條執行效率會更高。其中,HHVM(HipHop Virtual Machine,HHVM是一個Facebook開源的PHP虛擬機)就採用JIT,讓他們的PHP性能測試提升了一個數量級,放出一個令人震驚的測試結果,也讓我們直觀地認為JIT是一項點石成金的強大技術。

而實際上,在2013年的時候,鳥哥和Dmitry(PHP語言核心開發者之一)就曾經在PHP5.5的版本上做過一個JIT的嘗試(並沒有發布)。 PHP5.5的原來的執行流程,是將PHP程式碼透過詞法和語法分析,編譯成opcode字節碼(格式和組譯有點像),然後,Zend引擎讀取這些opcode指令,逐條解析執行。

PHP7革新与性能优化 - 徐汉彬Hansion - 技术行者

  

而他們在opcode環節後引入了類型推論(TypeInf),然後透過JIT產生ByteCodes,然後再執行。

 PHP7革新与性能优化 - 徐汉彬Hansion - 技术行者

於是,在benchmark(測試程式)中得到令人興奮的結果,實現JIT後效能比PHP5.5提升了8倍。然而,當他們把這個優化放入到實際的專案WordPress(一個開源部落格專案)中,卻幾乎看不見效能的提升,得到了一個令人費解的測試結果。

於是,他們使用Linux下的profile類型工具,對程式執行進行CPU耗時佔用分析。

執行100次WordPress的CPU消耗的分佈(截圖來自PPT):

 PHP7革新与性能优化 - 徐汉彬Hansion - 技术行者

註解:

21%CPU時間花費在記憶體管理。

12%CPU時間花費在hash table操作,主要是PHP陣列的增刪改查。

30%CPU時間花費在內建函數,例如strlen。

25%CPU時間花費在VM(Zend引擎)。

經過分析後,得到了兩個結論:

(1)JIT產生的ByteCodes如果太大,會造成CPU快取命中率下降(CPU Cache Miss)

在PHP5.5的程式碼裡,因為並沒有明顯類型定義,只能靠類型推斷。盡可能將可以推斷出來的變數類型,定義出來,然後,結合類型推斷,將非該類型的分支程式碼去掉,產生直接可執行的機器碼。然而,類型推斷不能推斷出全部類型,在WordPress中,能夠推斷出來的類型資訊只有不到30%,能夠減少的分支代碼有限。導致JIT以後,直接產生機器碼,產生的ByteCodes太大,最終造成CPU快取命中大幅下降(CPU Cache Miss)。

CPU快取命中是指,CPU在讀取並執行指令的過程中,如果需要的資料在CPU一級快取(L1)中讀取不到,就必須往下繼續尋找,一直到二級快取(L2)和三級快取(L3),最終會嘗試到記憶體區域裡尋找所需的指令數據,而記憶體和CPU快取之間的讀取耗時差距可以達到100倍級別。所以,ByteCodes如果過大,執行指令數量過多,導致多層快取無法容納如此之多的數據,部分指令將不得不被存放到記憶體區域。

 PHP7革新与性能优化 - 徐汉彬Hansion - 技术行者 

CPU的各級快取的大小也是有限的,下圖是Intel i7 920的設定資訊:

 PHP7革新与性能优化 - 徐汉彬Hansion - 技术行者

因此,CPU快取命中率下降會帶來嚴重的耗時增加,另一方面,JIT帶來的效能提升,也被它所抵消掉了。

透過JIT,可以降低VM的開銷,同時,透過指令優化,可以間接降低記憶體管理的開發,因為可以減少記憶體分配的次數。然而,對於真實的WordPress專案來說,CPU耗時只有25%在VM上,主要的問題和瓶頸實際上並不在VM上。因此,JIT的最佳化計劃,最後沒有被列入該版本的PHP7特性。不過,它很可能會在更後面的版本中實現,這一點也非常值得我們期待哈。

(2)JIT效能的提升效果取決於專案的實際瓶頸

JIT在benchmark中有大幅的提升,是因為程式碼量比較少,最終產生的ByteCodes也比較小,同時主要的開銷是在VM中。而應用在WordPress實際專案中並沒有明顯的效能提升,原因WordPress的程式碼量要比benchmark大得多,雖然JIT降低了VM的開銷,但是因為ByteCodes太大而又造成CPU快取命中下降和額外的內存開銷,最終變成沒有提升。

不同類型的專案會有不同的CPU開銷比例,也會得到不同的結果,脫離實際專案的效能測試,並不具有很好的代表性。

2. Zval的改變

PHP的各種類型的變量,其實,真正儲存的載體就是Zval,它特徵是海納百川,有容乃大。從本質上看,它是C語言實現的一個結構體(struct)。對於寫PHP的同學,可以將它粗略地理解為是一個類似array數組的東西。

PHP5的Zval,記憶體佔據24個位元組(截圖來自PPT):

 PHP7革新与性能优化 - 徐汉彬Hansion - 技术行者

PHP7的Zval,記憶體佔據16個位元組(截圖來自PPT):

 PHP7革新与性能优化 - 徐汉彬Hansion - 技术行者

Zval從24個位元組下降到16個位元組,為什麼會下降呢,這裡需要補一點點的C語言基礎,輔助不熟悉C的同學理解。 struct和union(聯合體)有點不同,Struct的每一個成員變數要各自佔據一塊獨立的記憶體空間,而union裡的成員變數是共用一塊記憶體空間(也就是說修改其中一個成員變量,公有空間就被修改了,其他成員變數的記錄也就沒有了)。因此,雖然成員變數看起來多了不少,但是實際佔據的記憶體空間卻下降了。

除此之外,還有被明顯改變的特性,部分簡單型別不再使用引用。

Zval結構圖(源自PPT):

 PHP7革新与性能优化 - 徐汉彬Hansion - 技术行者

#

圖中Zval的由2個64bits(1位元組=8bit,bit是「位元」)組成,如果變數類型是long、bealoon這些長度不超過64bit的,則直接儲存到value中,就沒有下面的引用了。當變數類型是array、objec、string等超過64bit的,value儲存的就是一個指針,指向真實的儲存結構位址。

對於簡單的變數類型來說,Zval的儲存變得非常簡單且有效率。

不需要引用的類型:NULL、Boolean、Long、Double

需要引用的類型:String、Array、Object、Resource、Reference

3. 內部類型zend_string

Zend_string是實際儲存字串的結構體,實際的內容會儲存在val(char,字元型)中,而val是一個char數組,長度為1(方便成員變數佔位)。

 PHP7革新与性能优化 - 徐汉彬Hansion - 技术行者

結構體最後一個成員變數採用char數組,而不是使用char*,這裡有一個小最佳化技巧,可以降低CPU的cache miss。

如果使用char數組,當malloc申請上述結構體內存,是申請在同一片區域的,通常是長度是sizeof(_zend_string)   實際char存儲空間。但是,如果使用char*,那個這個位置儲存的只是一個指針,真實的儲存又在另外一片獨立的記憶體區域內。

使用char[1]和char*的記憶體分配比較:

 PHP7革新与性能优化 - 徐汉彬Hansion - 技术行者

#從邏輯實作的角度來看,兩者其實也沒有太大差別,效果很類似。而實際上,當這些記憶體區塊被載入到CPU的中,就顯得非常不一樣。前者因為是連續分配在一起的同一塊內存,在CPU讀取時,通常都可以一同獲得(因為會在同一級緩存中)。而後者,因為是兩塊內存的數據,CPU讀取第一塊內存的時候,很可能第二塊內存數據不在同一級緩存中,使CPU不得不往L2(二級緩存)以下尋找,甚至到記憶體區域查到想要的第二塊記憶體資料。這裡就會造成CPU Cache Miss,兩者的耗時最高可以相差100倍。

另外,在字串複製的時候,採用引用賦值,zend_string可以避免的記憶體拷貝。

6. PHP數組的變化(HashTable和Zend Array)

在編寫PHP程式過程中,使用最頻繁的類型莫過於數組,PHP5的數組採用HashTable實作。如果用比較粗略的概括方式來說,它算是一個支援雙向鍊錶的HashTable,不僅支援透過陣列的key來做hash映射存取元素,也能透過foreach以存取雙向鍊錶的方式遍歷數組元素。

PHP5的HashTable(截圖來自於PPT):

 PHP7革新与性能优化 - 徐汉彬Hansion - 技术行者

這個圖看起來很複雜,各種指標跳來跳去,當我們透過key值存取一個元素內容的時候,有時需要3次的指標跳躍才能找對需要的內容。而最重要的一點,就在於這些數組元素存儲,都是分散在各個不同的記憶體區域的。同理可得,在CPU讀取的時候,因為它們就很可能不在同一級快取中,會導致CPU不得不到下級快取甚至記憶體區域查找,也就是造成CPU快取命中下降,進而增加更多的耗時。

PHP7的Zend Array(截圖來自PPT):

 PHP7革新与性能优化 - 徐汉彬Hansion - 技术行者

新版本的陣列結構,非常簡潔,讓人眼睛一亮。最大的特點是,整塊的陣列元素和hash映射表全部連接在一起,被分配在同一塊記憶體內。如果是遍歷一個整數的簡單型別數組,效率會非常快,因為,數組元素(Bucket)本身是連續分配在同一塊記憶體裡,而且,數組元素的zval會把整數元素儲存在內部,也不再有指針外鏈,全部資料都儲存在目前記憶體區域內。當然,最重要的是,它能夠避免CPU Cache Miss(CPU快取命中率下降)。

Zend Array的變更:

(1) 陣列的value預設為zval。

(2) HashTable的大小從72降到56位元組,減少22%。

(3) Buckets的大小從72下降到32字節,減少50%。

(4) 數組元素的Buckets的記憶體空間是一同分配的。

(5) 陣列元素的key(Bucket.key)指向zend_string。

(6) 陣列元素的value被嵌入到Bucket中。

(7) 降低CPU Cache Miss。

 

7. 函數呼叫機制(Function Calling Convention)

PHP7改善了函數的呼叫機制,透過最佳化參數傳遞的環節,減少了一些指令,提高執行效率。

PHP5的函數呼叫機制(截圖來自於PPT):

PHP7革新与性能优化 - 徐汉彬Hansion - 技术行者

圖中,在vm堆疊中的指令send_val和recv參數的指令是相同,PHP7透過減少這兩條重複,來達到對函數呼叫機制的底層最佳化。

PHP7的函數呼叫機制(截圖來自於PPT):

 PHP7革新与性能优化 - 徐汉彬Hansion - 技术行者

 

8. 透過巨集定義和內聯函數(inline ),讓編譯器提前完成部分工作

C語言的巨集定義會被在預處理階段(編譯階段)執行,提前將部分工作完成,無需在程式執行時分配內存,能夠實現類似函數的功能,卻沒有函數呼叫的壓棧、彈棧開銷,效率會比較高。內聯函數也類似,在預處理階段,將程式中的函數替換為函數體,真實運行的程式執行到這裡,就不會產生函數呼叫的開銷。

PHP7在這方面做了不少的最佳化,將不少需要在執行階段要執行的工作,放到了編譯階段。例如參數類型的判斷(Parameters Parsing),因為這裡涉及的都是固定的字元常數,因此,可以放到編譯階段來完成,進而提升後續的執行效率。

例如下圖中處理傳遞參數類型的方式,從左邊的寫法,最佳化為右邊巨集的寫法。

 PHP7革新与性能优化 - 徐汉彬Hansion - 技术行者

 

三、小結

鳥哥的PPT裡放過一組對比數據,就是WordPress在PHP5.6執行100次會產生70億次的CPU指令執行數目,而在PHP7中只需要25億次,減少64.2%,這是一個令人震撼的數據。

在鳥哥的整個分享中,給我最深刻的一個觀點是:要注意細節,很多細小的優化,一點點持續地積累,積少成多,最終匯聚為驚豔的成果。為山九仞,豈一日之功,我想大概也是這個道理。

毫無疑問,PHP7在效能方面實現跨越式的提升,如果能夠將這些成果應用在PHP的Web系統中,也許我們只需要更少的機器,就可以支撐起更高請求量的服務。 PHP7正式版的發布,令人充滿無限憧憬。

以上是PHP7更新及效能最佳化的介紹(圖文)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

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

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

php7.0安裝了插件還是顯示未安裝怎麼辦 php7.0安裝了插件還是顯示未安裝怎麼辦 Apr 02, 2024 pm 07:39 PM

解決 PHP 7.0 中插件未顯示已安裝問題的方法:檢查插件配置並啟用插件。重新啟動 PHP 以套用配置變更。檢查插件檔案權限,確保其正確。安裝遺失的依賴項,以確保插件正常運作。如果其他步驟都失敗,則重建 PHP。其他可能原因包括外掛程式版本不相容、載入錯誤版本或 PHP 配置問題。

php7檢測tcp埠不好用怎麼解決 php7檢測tcp埠不好用怎麼解決 Mar 22, 2023 am 09:30 AM

在php5中,我們可以使用fsockopen()函數來偵測TCP埠。這個函數可以用來開啟一個網路連接和進行一些網路通訊。但是在php7中,fsockopen()函數可能會遇到一些問題,例如無法開啟連接埠、無法連接到伺服器等。為了解決這個問題,我們可以使用socket_create()函數和socket_connect()函數來偵測TCP埠。

php7.0怎麼安裝mongo擴展 php7.0怎麼安裝mongo擴展 Nov 21, 2022 am 10:25 AM

php7.0安裝mongo擴充的方法:1、建立mongodb使用者群組和使用者;2、下載mongodb原始碼包,並將原始碼包放到“/usr/local/src/”目錄下;3、進入“src/”目錄;4、解壓縮原始碼包;5、建立mongodb檔案目錄;6、將檔案複製到「mongodb/」目錄;7、建立mongodb設定檔並修改設定即可。

php7.0怎麼安裝部署 php7.0怎麼安裝部署 Nov 30, 2022 am 09:56 AM

php7.0安裝部署的方法:1、到PHP官網下載與本機系統對應的安裝版本;2、將下載的zip檔案解壓縮到指定目錄;3、開啟命令列窗口,在「E:\php7」目錄下運行“php -v”命令即可。

PHP 伺服器環境常見問題指南:快速解決常見難題 PHP 伺服器環境常見問題指南:快速解決常見難題 Apr 09, 2024 pm 01:33 PM

PHP伺服器環境常見的解決方法包括:確保已安裝正確的PHP版本和已複製相關檔案到模組目錄。暫時或永久停用SELinux。檢查並配置PHP.ini,確保已新增必要的擴充功能和進行正確設定。啟動或重新啟動PHP-FPM服務。檢查DNS設定是否有解析問題。

如何在系統重啟後自動設置unixsocket的權限? 如何在系統重啟後自動設置unixsocket的權限? Mar 31, 2025 pm 11:54 PM

如何在系統重啟後自動設置unixsocket的權限每次系統重啟後,我們都需要執行以下命令來修改unixsocket的權限:sudo...

php8和php7哪個好 php8和php7哪個好 Nov 16, 2023 pm 03:09 PM

PHP8相較於PHP7在效能、新特性和語法改進、型別系統、錯誤處理和擴充等方面都有一些優勢和改進。然而,選擇使用哪個版本要根據特定的需求和專案情況來決定。詳細介紹:1、效能提升,PHP8引進了Just-in-Time(JIT)編譯器,可以提高程式碼的執行速度;2、新特性和語法改進,PHP8支援命名參數和可選參數的聲明,使得函數呼叫更加靈活;引入了匿名類別、屬性的類型聲明等等。

在Docker環境中使用PECL安裝擴展時為什麼會報錯?如何解決? 在Docker環境中使用PECL安裝擴展時為什麼會報錯?如何解決? Apr 01, 2025 pm 03:06 PM

在Docker環境中使用PECL安裝擴展時報錯的原因及解決方法在使用Docker環境時,我們常常會遇到一些令人頭疼的問�...

See all articles