首頁  >  文章  >  後端開發  >  詳解PHP7新特性 What will be in PHP 7

詳解PHP7新特性 What will be in PHP 7

coldplay.xixi
coldplay.xixi轉載
2021-04-08 11:07:371581瀏覽

   詳解PHP7新特性 What will be in PHP 7

       PHP7将在2015年12月正式发布,PHP7 ,将会是PHP脚本语言的重大版本更新,同时将带来大幅的性能改进和新的特性,以及改进一些过时功能。 该发布版本将会专注在性能加强,源自PHP版本树中的phpng分支。在硅谷公司的ZendCon会议,PHP工具厂商Zend技术官方讨论phpng和 PHP7的进度。“(本次升级)真正专注于帮助业界的应用程序显著加强执行速度,再加上,我们在PHP中的其他改进,”Zend的首席执行官安迪特曼斯 (曾参与了PHP语言的持续开发和发展)表示。 

推荐(免费):PHP7

    我们来看看官网给出的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次的WordPressCPU消耗的分佈:

              

    ##註解:

    #################################################

    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 (<=>)  
    • ##Combined comparison Operator (<=>)  ## (<=>)
    • Return Type Declarations    返回類型宣告
    Scalar 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.

    #
    • 首先,PHP 6 已經存在,而且它是完全不同的東西。十進制系統(或更準確地說,我們擁有無限的數字)使我們很容易跳過一個版本,為未來的版本留下更多的內容。
    • #雖然其他PHP 6 確實從未這樣做過儘管它已正式發布,但它仍然是 php.net 進行的一個廣泛發布且知名的項目,與現在正在討論的版本絕對不會共享任何內容。任何知道 PHP 6 是什麼的人(有很多人)都會對這個即將推出的新版本的內容和功能產生強烈的誤解(本質上,它都是關於 Unicode 的)。
    • # PHP 6,即最初的PHP 6,已在許多PHP 會議上進行了詳細討論。它被視為已完成的交易,包括有關功能和行為的詳細解釋(由 php.net 開發人員,而不是“邪惡”的書籍作者)。
    • PHP 6 不僅在 Internals 社區中廣為人知,但圍繞整個PHP 社群。這是一個備受矚目的項目,許多(如果不是大多數)PHP 社群成員都知道。
    • 網路上存在大量有關原始 PHP 6 的 PHP 6 資訊。書籍是問題中最小的部分。
    • 與「我們為什麼跳到 7?」的「瑣事問題」不同,重複使用版本 6 可能會導致人們頭腦中的real 混亂,提供有關兩個完全不同的版本的充足信息,這些版本具有完全不同的功能集,但名稱完全相同。
    • 跳過版本在開源專案和商業產品中並不是史無前例或不常見的。 MariaDB 一路跳到了 10.0 版本以避免混淆,Netscape Communicator 直接跳過了 5.0 版本進入了 6.0,而 Symantec 則跳過了版本 13。每個都有不同的跳過原因,但共同點是跳過版本沒什麼大不了的。
    • 版本6 通常與動態語言世界中的失敗聯繫在一起。 PHP 6 失敗了; Perl 6 是失敗的。它實際上也與動態語言世界之外的故障相關 - MySQL 6 也存在但從未發布。將版本 6 視為失敗的看法 - 不是迷信,而是現實世界的事實(類似於「Vista」一詞與失敗的關聯) - 將對這個 PHP 版本產生不良影響。
    • 」案例for 6主要是對上述一些觀點的反駁,但沒有提供強有力的理由來說明為什麼我們「不應該」跳過版本6。如果我們使用PHP 7,最糟糕的情況是我們不必要地跳過版本。我們仍然可以無限地供應主要版本以供將來使用。然而,如果我們選擇6 而不是7 - 最壞的情況是我們社區中普遍存在混亂以及對該版本的潛在負面看法。

    #支持的SAPI

    • #cli

    • cgi

    • #fpm

    • apache (FastCGI 和FPM 可能會快得多如果mod_php 建構成PIC)

    • apache2handler

    支援的擴充功能

    • bcmath

    • bz2

    • 「行事曆

    • ##com_dotnet

      #

    • ctype

    • curl

    • #date

    • ##dba

    • dom

    • #附魔

    • #ereg

    • exif

    • 檔案資訊

    • 過濾器

    • ##ftp
    • gd
    • gettext
    • gmp
    • hash
    • iconv
    • imap
    • #intl
    • json
    • ldap
    • libxml
    • #mbstring
    • ##mcrypt
    • mysql
    • mysqli
    • #mysqlnd
    • odbc (已測試帶有unixODBC 和MySQL 驅動程式)
    • openssl
    • OPcache
    • pcntl
    • pcre
    • PDO
    • #pdo_firebird
    • ###################################################################################################### #pdo_mysql### ########PDO_ODBC(使用unixODBC 和MySQL 驅動程式進行測試)###########pdo_pgsql############pdosqlite ###### ######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############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,就是為你的專案量身打造的最高效能的編譯版本。


    暫時就這麼多吧,以後想起來再加,歡迎大家試試,thanks。

    以上是詳解PHP7新特性 What will be in PHP 7的詳細內容。更多資訊請關注PHP中文網其他相關文章!

    陳述:
    本文轉載於:csdn.net。如有侵權,請聯絡admin@php.cn刪除