PHP7新特性 What will be in PHP 7/PHPNG
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
本文結合php官網和鳥哥相關文章總結:
官網:http://www.php7.ca/ https://wiki.php.net/phpng
PHP7將在2015年12月正式釋出,PHP7 ,將會是PHP指令碼語言的重大版本更新,同時將帶來大幅的效能改進和新的特性,以及改進一些過時功能。 該 釋出版本將會專注在效能加強,源自PHP版本樹中的phpng分支。在矽谷公司的ZendCon會議,PHP工具廠商Zend技術官方討論phpng和 PHP7的進度。“(本次升級)真正專注於幫助業界的應用程式顯著加強執行速度,再加上,我們在PHP中的其他改進,”Zend的執行長安迪特曼斯 (曾參與了PHP語言的持續開發和發展)表示。
我們來看看官網給出的php7 引擎和特性:
PHP7引擎( What will be in PHP 7 / PHPNG )
- 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),因為這裡涉及的都是固定的字元常量,因此,可以放到到編譯階段來完成,進而提升後續的執行效率。
例如下圖中處理傳遞引數型別的方式,從左邊的寫法,優化為右邊巨集的寫法。
PHP 7.0.0 RC 2 Released新特性
- 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新增的操作符 <=>,perfectfunction 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.
Supported SAPI
- cli
- cgi
- fpm
- apache (FastCGI and FPM might be significantly faster if mod_php is built as PIC)
- apache2handler
Supported Extensions
- 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
Unsupported Extensions (not converted yet)
- interbase
- mssql
- oci8
- pdo_dblib
- pdo_oci
- sybase_ct
讓PHP 7達到最高效能的幾個Tips
鳥哥部落格:http://www.laruence.com/2015/12/04/3086.htmlPHP7 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,就是為你的專案量身打造的最高效能的編譯版本。
暫時就這麼多吧,以後想起來再加,歡迎大家嘗試,thanks。