您好,登錄后才能下訂單哦!
小編給大家分享一下PHP7引擎的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
PHP7將在2015年12月正式發布,PHP7 ,將會是PHP腳本語言的重大版本更新,同時將帶來大幅的性能改進和新的特性,以及改進一些過時功能。 該發布版本將會專注在性能加強,源自PHP版本樹中的phpng分支。在硅谷公司的ZendCon會議,PHP工具廠商Zend技術官方討論phpng和 PHP7的進度。“(本次升級)真正專注于幫助業界的應用程序顯著加強執行速度,再加上,我們在PHP中的其他改進,”Zend的首席執行官安迪特曼斯 (曾參與了PHP語言的持續開發和發展)表示。
我們來看看官網給出的php7 引擎和特性:
Performance Improvements with the addition of PHPNG engine.(使用PHPNG引擎來提升性能)
JIT - Just in Time compiler (即時編輯器 JIT Compiler_百度百科)
Abstract Syntax Tree for compilation(抽象語法樹編譯)
Asynchronous refactoring of the I/O layer. 對I/O層的異步重構。
Multi-threaded build in Web Server多線程構建Web服務器
Expanded use of ->, [], (), {}, and :: operators 擴展使用 ->, [], (), {}, 和 :: 符號
100% increase in performance性能提升 100% (應該是QPS)
Cool Name: PHPNG 酷名:PHPNG引擎
1) PHP7速度是 PHP5.6 的兩倍
2) JIT - Just in Time compiler (即時編輯器)
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指令,逐條解析執行。
而他們在opcode環節后引入了類型推斷(TypeInf),然后通過JIT生成ByteCodes,然后再執行。
于是,在benchmark(測試程序)中得到令人興奮的結果,實現JIT后性能比PHP5.5提升了8倍。然而,當他們把這個優化放入到實際的項目WordPress(一個開源博客項目)中,卻幾乎看不見性能的提升,得到了一個令人費解的測試結果。
于是,他們使用Linux下的profile類型工具,對程序執行進行CPU耗時占用分析。
執行100次WordPress的CPU消耗的分布:
注解:
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如果過大,執行指令數量過多,導致多級緩存無法容納如此之多的數據,部分指令將不得不被存放到內存區域。
CPU的各級緩存的大小也是有限的,下圖是Intel i7 920的配置信息:
因此,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開銷比例,也會得到不同的結果,脫離實際項目的性能測試,并不具有很好的代表性。
3). Zval的改變
PHP的各種類型的變量,其實,真正存儲的載體就是Zval,它特點是海納百川,有容乃大。從本質上看,它是C語言實現的一個結構體(struct)。對于寫PHP的同學,可以將它粗略理解為是一個類似array數組的東西。
PHP5的Zval,內存占據24個字節:
PHP7的Zval,內存占據16個字節:
Zval從24個字節下降到16個字節,為什么會下降呢,這里需要補一點點的C語言基礎,輔助不熟悉C的同學理解。struct和union(聯合體)有點不同,Struct的每一個成員變量要各自占據一塊獨立的內存空間,而union里的成員變量是共用一塊內存空間(也就是說修改其中一個成員變量,公有空間就被修改了,其他成員變量的記錄也就沒有了)。因此,雖然成員變量看起來多了不少,但是實際占據的內存空間卻下降了。
除此之外,還有被明顯改變的特性,部分簡單類型不再使用引用。
Zval結構圖:
圖中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
4) . 內部類型zend_string
Zend_string是實際存儲字符串的結構體,實際的內容會存儲在val(char,字符型)中,而val是一個char數組,長度為1(方便成員變量占位)。
結構體最后一個成員變量采用char數組,而不是使用char*,這里有一個小優化技巧,可以降低CPU的cache miss。
如果使用char數組,當malloc申請上述結構體內存,是申請在同一片區域的,通常是長度是sizeof(_zend_string) + 實際char存儲空間。但是,如果使用char*,那個這個位置存儲的只是一個指針,真實的存儲又在另外一片獨立的內存區域內。
使用char[1]和char*的內存分配對比:
從邏輯實現的角度來看,兩者其實也沒有多大區別,效果很類似。而實際上,當這些內存塊被載入到CPU的中,就顯得非常不一樣。前者因為是連續分配在一起的同一塊內存,在CPU讀取時,通常都可以一同獲得(因為會在同一級緩存中)。而后者,因為是兩塊內存的數據,CPU讀取第一塊內存的時候,很可能第二塊內存數據不在同一級緩存中,使CPU不得不往L2(二級緩存)以下尋找,甚至到內存區域查到想要的第二塊內存數據。這里就會引起CPU Cache Miss,而兩者的耗時最高可以相差100倍。
另外,在字符串復制的時候,采用引用賦值,zend_string可以避免的內存拷貝。
5). PHP數組的變化(HashTable和Zend Array)
在編寫PHP程序過程中,使用最頻繁的類型莫過于數組,PHP5的數組采用HashTable實現。如果用比較粗略的概括方式來說,它算是一個支持雙向鏈表的HashTable,不僅支持通過數組的key來做hash映射訪問元素,也能通過foreach以訪問雙向鏈表的方式遍歷數組元素。
PHP5的HashTable:
這個圖看起來很復雜,各種指針跳來跳去,當我們通過key值訪問一個元素內容的時候,有時需要3次的指針跳躍才能找對需要的內容。而最重要的一點,就在于這些數組元素存儲,都是分散在各個不同的內存區域的。同理可得,在CPU讀取的時候,因為它們就很可能不在同一級緩存中,會導致CPU不得不到下級緩存甚至內存區域查找,也就是引起CPU緩存命中下降,進而增加更多的耗時。
PHP7的Zend Array(截圖來源于PPT):
新版本的數組結構,非常簡潔,讓人眼前一亮。最大的特點是,整塊的數組元素和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。
6). 函數調用機制(Function Calling Convention)
PHP7改進了函數的調用機制,通過優化參數傳遞的環節,減少了一些指令,提高執行效率。
PHP5的函數調用機制(截圖來自于PPT):
圖中,在vm棧中的指令send_val和recv參數的指令是相同,PHP7通過減少這兩條重復,來達到對函數調用機制的底層優化。
PHP7的函數調用機制(截圖來自于PPT):
7). 通過宏定義和內聯函數(inline),讓編譯器提前完成部分工作
C語言的宏定義會被在預處理階段(編譯階段)執行,提前將部分工作完成,無需在程序運行時分配內存,能夠實現類似函數的功能,卻沒有函數調用的壓棧、彈棧開銷,效率會比較高。內聯函數也類似,在預處理階段,將程序中的函數替換為函數體,真實運行的程序執行到這里,就不會產生函數調用的開銷。
PHP7在這方面做了不少的優化,將不少需要在運行階段要執行的工作,放到了編譯階段。例如參數類型的判斷(Parameters Parsing),因為這里涉及的都是固定的字符常量,因此,可以放到到編譯階段來完成,進而提升后續的執行效率。
例如下圖中處理傳遞參數類型的方式,從左邊的寫法,優化為右邊宏的寫法。
Improved performance: PHP 7 is up to twice as fast as PHP 5.6 :性能是php5.6的兩倍
Consistent 64-bit support 支持64位,統一不同平臺下的整型長度,字符串和文件上傳都支持大于2GB。
Many fatal errors are now Exceptions 更多Error錯誤可以進行異常處理
Removal of old and unsupported SAPIs and extensions 移除了舊的和不支持的 SAPIs 和擴展
The null coalescing operator (??) null 合并操作符(??)
Combined comparison Operator (<=>) 結合比較運算符 (<=>)
Return Type Declarations 返回類型聲明
Scalar Type Declarations 標量類型聲明
Anonymous Classes 匿名類
具體例子說明:
更多的Error變為可捕獲的Exception
PHP7 實現了一個全局的throwable接口,原來的Exception和部分Error都實現了這個接口(interface), 以接口的方式定義了異常 的繼承結構。于是,PHP7中更多的Error變為可捕獲的Exception返回給開發者,如果不進行捕獲則為Error,如果捕獲就變為一個可在程序 內處理的Exception。這些可被捕獲的Error通常都是不會對程序造成致命傷害的Error,例如函數不存。PHP7進一步方便開發者處理,讓開 發者對程序的掌控能力更強。因為在默認情況下,Error會直接導致程序中斷,而PHP7則提供捕獲并且處理的能力,讓程序繼續執行下去,為程序員提供更 靈活的選擇。
例如,執行一個我們不確定是否存在的函數,PHP5兼容的做法是在函數被調用之前追加的判斷function_exist,而PHP7則支持捕獲Exception的處理方式。
如下圖中的例子
AST(Abstract Syntax Tree,抽象語法樹)
AST在PHP編譯過程作為一個中間件的角色,替換原來直接從解釋器吐出opcode的方式,讓解釋器(parser)和編譯器(compliler)解耦,可以減少一些Hack代碼,同時,讓實現更容易理解和可維護。
PHP5:
PHP7:
更多AST信息:https://wiki.php.net/rfc/abstract_syntax_tree
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
Combined comparison Operator (<=>) 結合比較運算符 (<=>)
// PHP 7之前的寫法:比較兩個數的大小 function order_func($a, $b) { return ($a < $b) ? -1 : (($a > $b) ? 1 : 0); } // PHP新增的操作符 <=>,perfect function order_func($a, $b) { return $a <=> $b; }
Return Type Declarations 返回類型聲明 和Scalar Type Declarations 標量類型聲明
PHP語言一個非常重要的特點就是“弱類型”,它讓PHP的程序變得非常容易編寫,新手接觸PHP能夠快速上手,不過,它也伴隨著一些爭議。支持變量類型的定義,可以說是革新性質的變化,PHP開始以可選的方式支持類型定義。除此之外,還引入了一個開關指令declare(strict_type=1);,當這個指令一旦開啟,將會強制當前文件下的程序遵循嚴格的函數傳參類型和返回類型。
例如一個add函數加上類型定義,可以寫成這樣:
如果配合強制類型開關指令,則可以變為這樣:
如果不開啟strict_type,PHP將會嘗試幫你轉換成要求的類型,而開啟之后,會改變PHP就不再做類型轉換,類型不匹配就會拋出錯誤。對于喜歡“強類型”語言的同學來說,這是一大福音。
更為詳細的介紹: https://wiki.php.net/rfc/scalar_type_hints_v5 PHP7標量類型聲明RFC
為啥直接PHP5.6跳到PHP7(Reasons given why we need to skip to PHP 7)
There are several reasons of why we shouldn't reuse version 6 for the next major version of PHP.
First and foremost, PHP 6 already existed and it was something completely different. The decimal system (or more accurately the infinite supply of numbers we have) makes it easy for us to skip a version, with plenty more left for future versions to come.
While it's true that the other PHP 6 never reached General Availability, it was still a very widely published and well-known project conducted by php.net that will share absolutely nothing with the version that is under discussion now. Anybody who knew what PHP 6 is (and there are many) will have a strong misconception in his or her mind as to the contents and features of this new upcoming version (essentially, that it's all about Unicode).
PHP 6, the original PHP 6, has been discussed in detail in many PHP conferences. It was taught to users as a done-deal, including detailed explanations about features and behavior (by php.net developers, not 'evil' book authors).
PHP 6 was widely known not only within the Internals community, but around the PHP community at large. It was a high profile project that many - if not most - PHP community members knew about.
There's lots of PHP 6 information, about the original PHP 6, that exists around the web. Books are the smallest part of the problem.
Unlike the 'trivia question' of 'why did we skip into 7?', reusing version 6 is likely to call real confusion in people's minds, with ample information on two completely different versions with entirely different feature sets that have the exact same name.
Skipping versions isn't unprecedented or uncommon in both open source projects and commercial products. MariaDB, jumped all the way up to version 10.0 to avoid confusion, Netscape Communicator skipped version 5.0 directly into 6.0, and Symantec skipped version 13. Each and every one of those had different reasons for the skipping, but the common denominator is that skipping versions is hardly a big deal.
Version 6 is generally associated with failure in the world of dynamic languages. PHP 6 was a failure; Perl 6 was a failure. It's actually associated with failure also outside the dynamic language world - MySQL 6 also existed but never released. The perception of version 6 as a failure - not as a superstition but as a real world fact (similar to the association of the word 'Vista' with failure) - will reflect badly on this PHP version.
The case for 6 is mostly a rebuttal of some of the points above, but without providing a strong case for why we *shouldn't* skip version 6. If we go with PHP 7, the worst case scenario is that we needlessly skipped a version. We'd still have an infinite supply of major versions at our disposal for future use. If, however, we pick 6 instead of 7 - the worst case scenario is widespread confusion in our community and potential negative perception about this version.
cli
cgi
fpm
apache (FastCGI and FPM might be significantly faster if mod_php is built as PIC)
apache2handler
bcmath
bz2
calendar
com_dotnet
ctype
curl
date
dba
dom
enchant
ereg
exif
fileinfo
filter
ftp
gd
gettext
gmp
hash
iconv
imap
intl
json
ldap
libxml
mbstring
mcrypt
mysql
mysqli
mysqlnd
odbc (tested with unixODBC and MySQL driver)
openssl
OPcache
pcntl
pcre
PDO
pdo_firebird
pdo_mysql
PDO_ODBC (tested with unixODBC and MySQL driver)
pdo_pgsql
pdo_sqlite
pgsql
Phar
posix
pspell
readline
recode
Reflection
session
shmop
SimpleXML
snmp
soap
sockets
SPL
sqlite3
standard
sysvmsg
sysvsem
sysvshm
tidy
tokenizer
wddx
xml
xmlreader
xmlwriter
xsl
zip
zlib
interbase
mssql
oci8
pdo_dblib
pdo_oci
sybase_ct
PHP7 VS PHP5.6
1、Opcache
記得啟用Zend Opcache,因為PHP7即使不啟用Opcache速度也比PHP-5.6啟用了Opcache快,所以之前測試時期就發生了有人一直沒有啟用Opcache的事情。啟用Opcache非常簡單,在php.ini配置文件中加入:
zend_extension=opcache.so
opcache.enable=1
opcache.enable_cli=1"
2、使用新的編譯器
使用新一點的編譯器,推薦GCC 4.8以上,因為只有GCC 4.8以上PHP才會開啟Global Register for opline and execute_data支持,這個會帶來5%左右的性能提升(Wordpres的QPS角度衡量)
其實GCC 4.8以前的版本也支持,但是我們發現它支持的有Bug,所以必須是4.8以上的版本才會開啟這個特性。
3、HugePage
我之前的文章也介紹過: 讓你的PHP7更快之Hugepage ,首先在系統中開啟HugePages,然后開啟Opcache的huge_code_pages。
以我的CentOS 6.5為例,通過:
$sudo sysctl vm.nr_hugepages=512
分配512個預留的大頁內存:
$ cat /proc/meminfo | grep Huge
AnonHugePages: 106496 kB
HugePages_Total: 512
HugePages_Free: 504
HugePages_Rsvd: 27
HugePages_Surp: 0
Hugepagesize: 2048 kB
然后在php.ini中加入:
opcache.huge_code_pages=1
這樣一來,PHP會把自身的text段,以及內存分配中的huge都采用大內存頁來保存,減少TLB miss,從而提高性能。
4、Opcache file cache
開啟Opcache File Cache(實驗性),通過開啟這個,我們可以讓Opcache把opcode緩存緩存到外部文件中,對于一些腳本,會有很明顯的性能提升。
在php.ini中加入:
opcache.file_cache=/tmp
這樣PHP就會在/tmp目錄下Cache一些Opcode的二進制導出文件,可以跨PHP生命周期存在。
5、PGO
我之前的文章: 讓你的PHP7更快(GCC PGO) 也介紹過,如果你的PHP是專門為一個項目服務,比如只是為你的Wordpress,或者drupal,或者其他什么,那么你就可以嘗試通過PGO,來提升PHP,專門為你的這個項目提高性能。
具體的,以wordpress 4.1為優化場景。首先在編譯PHP的時候首先:
$ make prof-gen
然后用你的項目訓練PHP,比如對于Wordpress:
$ sapi/cgi/php-cgi -T 100 /home/huixinchen/local/www/htdocs/wordpress/index.php >/dev/null
也就是讓php-cgi跑100遍wordpress的首頁,從而生成一些在這個過程中的profile信息。
最后:
$ make prof-clean
$ make prof-use
這個時候你編譯得到的PHP7,就是為你的項目量身打造的最高性能的編譯版本。
以上是“PHP7引擎的示例分析”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。