<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # 二、跨越式的性能突破:全速前進 ## 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 - 技術行者](http://box.kancloud.cn/2015-09-16_55f901d58596b.png) 而他們在opcode環節后引入了類型推斷(TypeInf),然后通過JIT生成ByteCodes,然后再執行。 ?![PHP7革新與性能優化 - 徐漢彬Hansion - 技術行者](http://box.kancloud.cn/2015-09-16_55f901d5ce8d0.png) 于是,在benchmark(測試程序)中得到令人興奮的結果,實現JIT后性能比PHP5.5提升了8倍。然而,當他們把這個優化放入到實際的項目WordPress(一個開源博客項目)中,卻幾乎看不見性能的提升,得到了一個令人費解的測試結果。 于是,他們使用Linux下的profile類型工具,對程序執行進行CPU耗時占用分析。 執行100次WordPress的CPU消耗的分布(截圖來自PPT): ?![PHP7革新與性能優化 - 徐漢彬Hansion - 技術行者](http://box.kancloud.cn/2015-09-16_55f901d5f2f52.png) 注解: 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 - 技術行者](http://box.kancloud.cn/2015-09-16_55f901d637dfd.png)? CPU的各級緩存的大小也是有限的,下圖是Intel?i7?920的配置信息: ?![PHP7革新與性能優化 - 徐漢彬Hansion - 技術行者](http://box.kancloud.cn/2015-09-16_55f901d666f94.png) 因此,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 - 技術行者](http://box.kancloud.cn/2015-09-16_55f901db9822e.png) PHP7的Zval,內存占據16個字節(截圖來自PPT): ?![PHP7革新與性能優化 - 徐漢彬Hansion - 技術行者](http://box.kancloud.cn/2015-09-16_55f901dbc7ad1.png) Zval從24個字節下降到16個字節,為什么會下降呢,這里需要補一點點的C語言基礎,輔助不熟悉C的同學理解。struct和union(聯合體)有點不同,Struct的每一個成員變量要各自占據一塊獨立的內存空間,而union里的成員變量是共用一塊內存空間(也就是說修改其中一個成員變量,公有空間就被修改了,其他成員變量的記錄也就沒有了)。因此,雖然成員變量看起來多了不少,但是實際占據的內存空間卻下降了。 除此之外,還有被明顯改變的特性,部分簡單類型不再使用引用。 Zval結構圖(來源于PPT中): ?![PHP7革新與性能優化 - 徐漢彬Hansion - 技術行者](http://box.kancloud.cn/2015-09-16_55f901e1092b8.png) 圖中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 - 技術行者](http://box.kancloud.cn/2015-09-16_55f901e136de9.png) 結構體最后一個成員變量采用char數組,而不是使用char*,這里有一個小優化技巧,可以降低CPU的cache?miss。 如果使用char數組,當malloc申請上述結構體內存,是申請在同一片區域的,通常是長度是sizeof(_zend_string)?+?實際char存儲空間。但是,如果使用char*,那個這個位置存儲的只是一個指針,真實的存儲又在另外一片獨立的內存區域內。 使用char[1]和char*的內存分配對比: ?![PHP7革新與性能優化 - 徐漢彬Hansion - 技術行者](http://box.kancloud.cn/2015-09-16_55f901e180051.png) 從邏輯實現的角度來看,兩者其實也沒有多大區別,效果很類似。而實際上,當這些內存塊被載入到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 - 技術行者](http://box.kancloud.cn/2015-09-16_55f901e1a4439.png) 這個圖看起來很復雜,各種指針跳來跳去,當我們通過key值訪問一個元素內容的時候,有時需要3次的指針跳躍才能找對需要的內容。而最重要的一點,就在于這些數組元素存儲,都是分散在各個不同的內存區域的。同理可得,在CPU讀取的時候,因為它們就很可能不在同一級緩存中,會導致CPU不得不到下級緩存甚至內存區域查找,也就是引起CPU緩存命中下降,進而增加更多的耗時。 PHP7的Zend?Array(截圖來源于PPT): ?![PHP7革新與性能優化 - 徐漢彬Hansion - 技術行者](http://box.kancloud.cn/2015-09-16_55f901e2037b9.png) 新版本的數組結構,非常簡潔,讓人眼前一亮。最大的特點是,整塊的數組元素和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 - 技術行者](http://box.kancloud.cn/2015-09-16_55f901e2331b2.jpg) 圖中,在vm棧中的指令send_val和recv參數的指令是相同,PHP7通過減少這兩條重復,來達到對函數調用機制的底層優化。 PHP7的函數調用機制(截圖來自于PPT): ?![PHP7革新與性能優化 - 徐漢彬Hansion - 技術行者](http://box.kancloud.cn/2015-09-16_55f901e7a500c.png) ## 8.?通過宏定義和內聯函數(inline),讓編譯器提前完成部分工作 C語言的宏定義會被在預處理階段(編譯階段)執行,提前將部分工作完成,無需在程序運行時分配內存,能夠實現類似函數的功能,卻沒有函數調用的壓棧、彈棧開銷,效率會比較高。內聯函數也類似,在預處理階段,將程序中的函數替換為函數體,真實運行的程序執行到這里,就不會產生函數調用的開銷。 PHP7在這方面做了不少的優化,將不少需要在運行階段要執行的工作,放到了編譯階段。例如參數類型的判斷(Parameters?Parsing),因為這里涉及的都是固定的字符常量,因此,可以放到到編譯階段來完成,進而提升后續的執行效率。 例如下圖中處理傳遞參數類型的方式,從左邊的寫法,優化為右邊宏的寫法。 ?![PHP7革新與性能優化 - 徐漢彬Hansion - 技術行者](http://box.kancloud.cn/2015-09-16_55f901e7d3cb5.jpg)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看