去年在安寧庄的時候, 有個同事闡述了一個觀點:php中的if else 在執行時考慮到效率的原因,不會按我們的代碼的順序一條一條去試,而是隨機找出一個分支,執行,如果不對,再隨機找到一個分支
當時由於種種原因,也沒過多去想這個問題,最近查了下資料,發現里面的學問還挺大的
php解釋器是由c編寫的,是個經編譯生成的二進制文件, 我們編寫的PHP代碼相當於這個C程序的參數,只不過這個參數是個一個的文件, 這個C程序要解析這個php文件,產生相應的opcode,再去執行opcode對應的函數,每一部操作都是由C函數來實現
查詢opcode含義的利器: http://www.laruence.com/2008/11/20/640.html#ZEND_JMP_.28Opcode_42.29
<?php if($a == 1){ echo "a is 1"; }else if($a == 2){ echo "a is 2"; }else{ echo "a is x"; }
對於上面的php代碼來說,最終執行的opcode是
------------------------------------------------------------------------------------- 2 0 E > IS_EQUAL ~0 !0, 1 1 > JMPZ ~0, ->4 3 2 > ECHO 'a+is+1' 4 3 > JMP ->9 4 > IS_EQUAL ~1 !0, 2 5 > JMPZ ~1, ->8 5 6 > ECHO 'a+is+2' 6 7 > JMP ->9 7 8 > ECHO 'a+is+x' 9 9 > > RETURN 1
可以看到在執行php時, 是一條一條去執行的
1.先判斷 $a 是否 等於 1
2.如果不等於1,為false, 就JMPZ 到第4條命令,去比較 $a 是否 等於2
如果等於1, echo "a is 1"; 然后 無條件跳轉 JMP 第9行 return 了
3.如果 $a 不等於2 ,即為false, 就JMPZ 到第8條命令, echo "a is x"
如果 $a 等於2,直接echo "a is 2", 然后執行 JMP 第9行 return 了
所以,php編寫的程序,對C函數來說,還是要一步的一步去執行的,關於具體php的分支實現,請點擊這里
如果這個文件被執行100次,有90次 $a=3, 那么解釋器每次都要判斷 $a 是否等於1和 $a 是否等於2, 盡管第三個分支是滿足條件的,如果是C編寫的程序, CPU會針對某種策略挑選一個分支來執行, 對應上面的分支來說,CPU會直接取出第三個分支的指令,然后執行。
從486開始,CPU開始具備流水線這個特性,指令流水線由5,6個不同功能的工作單元組成,將一個x86指令也拆分成5,6個步驟,分別送往不同的工作單元,來達到同時執行多個指令的目的,現在的CPU支持30級的流水線,也就意味着流水線上有30個工作單元,對應的X86指令也拆分成30個步驟。
注:CPU執行的是二進制數據,代碼經過匯編編譯后,生成一條條二進制指令
例如 int a=1; 對應的匯編是mov $1, %eax; 對應的機器碼可能是00011100011
在執行文件時,根據局部性原理,想關的指令都要加載到CPU緩存中,
一般一條指令的完成 分四個步驟:
1.取指令
2.翻譯指令 (看是賦值,還是計算,從內存什么地方取數據)
3.執行指令
4.寫指令結果 (要么寫回內存,要么寫到寄存器)
取指令 翻譯指令 執行指令 寫指令結果
命令1 命令1
命令2
取指令單元取出指令1后,翻譯指令單元開始 翻譯指令1時,取指令單元可 取出指令2了
如果CPU不這么做,等到指令1完成上面四個步驟后,指令2才開始進行,那效率太低了
流水化中的單元分的更詳細, 更多的指令可以並行處理,但速度不見得快,因為有分支的出現,如果沒有命中第一個分支,后面的指令將作廢, 需要清空后面所有的指令, 然后中載命中地址的指令,再運行
在有5個分支的情況下,若采取隨機挑選一個分支 執行的話,每次賭該分支命中的概率只有五分之一, 於是CPU分支預測功能就出現了。
分支預測分靜態和動態
靜態分支預測:由編譯器決定哪個分支可能被CPU命中,一般是第一個分支,即 if 后面的邏輯,而不是后面else的邏輯
動態分支預測:在CPU硬件中開辟一塊緩存,專門記錄每個分支最近幾次的命中情況,然后做出預測,顯然這種方法能及時調整策略,有更好的遠詹性,但CPU壓力會大些,不過還好。
分支地址只有在流水線指令執行階段才能計算出來,為了避免等待,需要在譯碼階段進行預測
Two-Level分支預測方法使用了兩種數據結構,一種是BHR(Branch History Register);而另一種是PHT(Pattern History Table)。其中BHR由k位組成(可理解為記錄K次某個分支的執行結果),用來記錄每一條轉移指令的歷史狀態,而PHT表含有2k個Entry組成,而每一個Entry由兩位Saturating Counter組成。BHR和PHT的關系如圖3‑10所示。
假設分支預測單元在使用Two-Level分支預測方法時,設置了一個PBHT表(Per-address Branch History Table)存放不同指令所對應的BHR。在PBHT表中所有BHR的初始值為全1,而在PHT表中所有Entry的初始值值為0b11。BHR在PBHT表中的使用方法與替換機制與Cache類似。
當分支預測單元分析預測轉移指令B的執行時,將首先從PBHT中獲得與轉移指令B對應的BHR,此時BHR為全1,因此CPU將從PHT的第11…11個Entry中獲得預測結果0b11,即Strongly Taken。轉移指令B執行完畢后,將實際執行結果Rc更新到BHR寄存器中,並同時更新PHT中對應的Entry。
當CPU再次預測轉移指令B的執行時,仍將根據BHR索引PHT表,並從對應Entry中獲得預測結果。而當指令B再次執行完畢后,將繼續更新BHR和PHT表中對應的Entry。當轉移指令的執行結果具有某種規律(Pattern)時,使用這種方法可以有效提高預測精度。如果轉移指令B的實際執行結果為001001001….001,而且k等於4時,CPU將以0010-0100-1001這樣的循環訪問BHR,因此CPU將分別從PHT表中的第0010、0100和1001個Entry中獲得准確的預測結果。
由以上描述可以發現,Two-Level分支預測法具有學習功能,並可以根據轉移指令的歷史記錄產生的模式,在PHT表中查找預測結果。該算法由T.Y. Yeh and Y.N. Patt在1991年提出,並在高性能處理器中得到了大規模應用。
Two-Level分支預測法具有許多變種。目前x86處理器主要使用“Local Branch Prediction”和“Global Branch Prediction”兩種算法。
在“Local Branch Prediction”算法中,每一個BHR使用不同的PHT表,Pentium II和Pentium III處理器使用這種算法。該算法的主要問題是當PBHT表的Entry數目增加時,PHT表將以指數速度增長,而且不能利用其它轉移指令的歷史信息進行分支預測。而在“Global Branch Prediction”算法中,所有BHR共享PHT表,Pentium M、Pentium Core和Core 2處理器使用這種算法。
在高性能處理器中,分支預測單元對一些特殊的分支指令如“Loop”和“Indirect跳轉指令”設置了“Loop Prediction”和“Indirect Prediction”部件優化這兩種分支指令的預測。此外分支預測單元,還設置了RSB(Return Stack Buffer),當CPU調用一個函數時,RSB將記錄該函數的返回地址,當函數返回時,將從RSB中獲得返回地址,而不必從堆棧中獲得返回地址,從而提高了函數返回的效率。
目前在高性能處理器中,動態分支預測的主要實現機制是CPU通過學習以往歷史信息,並進行預測,因而Neural branch predictors機制被引入,並取得了較為理想的效果,本節對這種分支預測技術不做進一步說明。目前指令的動態分支預測技術較為成熟,在高性能計算機中,分支預測的成功概率在95%~98%之間,而且很難進一步提高。
參考:http://blog.sina.com.cn/s/blog_6472c4cc0100qxd2.html
http://tonysuo.blogspot.hk/2013/12/computer-architecture-5.html
http://blog.hesey.net/2013/03/branch-prediction-in-pipeline.html
http://wenku.baidu.com/view/48833667ddccda38376bafa2.html
http://blog.sina.com.cn/s/blog_5a82024e0100e5lm.html
//大話處理器
http://blog.csdn.net/muxiqingyang/article/details/6677425
http://cyukang.com/2012/07/11/branch_prediction.html
http://blog.csdn.net/wahaha_nescafe/article/details/8500094
https://www.zhihu.com/question/23973128
http://blog.sina.com.cn/s/blog_6556314c0100hamf.html
http://blog.sina.com.cn/s/blog_6556314c0100hamt.html
http://blog.sina.com.cn/s/blog_6556314c0100hamj.html
http://blog.sina.com.cn/s/blog_6556314c0100hamh.html
https://www.zhihu.com/question/23973128