處理器是如何實現對程序的fetch的?分支預測是在前端解碼後就進行的嗎?
問題主要如題所示,這裡做一些補充。
我對取指的概念一直都不是非常了解,取指是從cache裡面取(部分已有過取指過程的在Trace Cache里直接取uop除外),請問這個指令究竟取的是什麼樣的指令。其最原始的是否是軟體寫下來的代碼。我對軟體編程是不怎麼懂的,軟體不是已經封裝過了嗎?處理器是對軟體的每行代碼進行讀取嗎?在原理上,處理器是否會先把高級語言轉為彙編語言然後再轉為機器語言呢?——還是說,彙編語言實際上也是為了理解CPU工作原理而出的東西呢?那麼取指是不是就是說讀軟體的每行代碼,隨後再由編譯器翻譯成彙編語言,並由CPU執行呢?(先不考慮這個x86轉uop的問題)指令是否可以簡單理解為每一行代碼?
第二個問題是,分支預測的執行具體是在哪個步驟,應該是在前端進行的,是否在解碼之後立刻就進行分支預測呢?——按照我對分支預測實現的理解,是否每段代碼,不管有沒有if分支的意思,都需要先到分支預測的buffer里轉一圈,由處理器判斷是否有分支和跳轉呢?——還是說解碼後仍有單元會簡單判斷是否為存在分支預測的必要,隨後再送往分支預測buffer?這一步應該是在去指令相關(寄存器重命名等)之前進行的吧?
好像是挺久之前的帖子了,不過最近正好在做於前端有關的工作所以來聊一聊。首先關於指令,樓里已經說的比較清楚了,從代碼到指令的翻譯工作由編譯器compiler來完成。值得一提的是compiler並不是單純的完成翻譯的工作,他也有優化調度代碼的作用,能讓pipeline更加efficient。舉兩個例子:
- rescheduling: 一條C代碼x=a+b;可能會被編譯為四條彙編語句,load a;load b;add;store x;假設5 stage pipeline:IF DE EX MEM WB,在load b時該指令處於MEM stage,而他的下一條指令add已經到達EX stage,但此時b的值還並未load出來。因此整個pipeline得被迫stall一個cycle。於是機智的compiler這時候可以在之後的指令中找出一條independent的指令塞在load b和add之間,原本被stall的cycle用來執行其他指令了,因為是independent所以即使被提前執行也不影響程序功能。
- Loop unrolling:C代碼:for(i=0;i&
好了,說了這麼多沒用的你是不是已經發現了呢,從C代碼到機器碼中間發生了什麼,你有問道「指令是不是每一行代碼」,CPU所取的代碼就是被編譯後的機器碼。關於取指令fetch,CPU通常不是一條一條取,而是一個packet一個packet取。一個packet可能包含多條指令。就是你有個program counter PC。他就像個指針,指向你的指令的地址,如果沒有跳轉就每次累加,一個packet一個packet的順序讀取指令,如果有跳轉,就將target address寫入到PC中,就自動取到對應的packet。PC一次累加多少由存儲結構和packet大小決定。由於packet包含多條指令,通常他們被推入一個queue裡面,queue里的指令可能會由hardware去檢查他們的dependency並且重新schedule,最終一次issue多條指令實現parallel execution。這就是所謂的superscalar啦。
最後來再來說說分支預測,大致分兩類:- static branch prediction,這類是沒有記憶效應的,複雜度較低,典型的有always predicted as taken, always non-taken,或者根據跳轉的方向預測,forward的branch就non-taken,backward則是taken。不難想像雖然機制簡單,但是變數太大,預測的準確率主要看人品。
- dynamic branch prediction,這類是通過記錄branch歷史的跳轉來進行預測:
- branch history table BHT,一個table可能有1k以上個entry,每一個entry對應某個branch上次的takeness也有可能每個entry是2 bit counter,這種又叫pattern history table PHT。一般用所對應的branch地址的lowest portion來index entry。這種叫做bimodal predictor。
- 2 level prediction,這個結構就複雜一些了,將branch地址的lowest portion與global history register(一般存儲了過去幾條branch的taken情況)相hash再index entry這種叫做gshare predictor。也可以有多個PHT,比如由PC來選擇PHT而由history register來選擇entry,變化很多。
- hybrid prediction,這種就是把前面的結構結合起來,更加更加複雜。你也許需要一個meta predictor,這個predictor不直接預測takeness,而是預測bimodal和gshare哪一個更可能預測對。因為對於不同的情況不同的predictor可能表現不同。你也有可能需要voter,比方三個predictor同時預測,voter可以投出最終的takeness。
- 當你有indirect branch時,target address同樣需要預測。這時候我們需要branch target buffer BTB。他就是一個cache,TAG就是CAM用來lookup對應的entry,然後每個entry儲存target address。
- 最後一種嚴格來說不是用來預測branch的,它用來處理push和pop指令,叫做call return stack。當有push指令時,我們把下一條指令的地址存放在這個stack里(實際上是cache)中。這樣下次pop的時候,直接把地址從stack中取出,這樣就能迅速回到原來的instruction stream。
你問了不少分支預測到底在哪的問題,不同的處理期結構都不太一樣。但是你說的沒錯,都在front end前端。實際上如果在back end那麼預測就沒有任何意義了。舉個例子:
奔4:P4處理器指令需要依次經過三個大模塊:http://1.in order front end 2.out of order engine 3.execution unit,execution unit就不說了,2裡面依次是reorder buffer,寄存器重命名,uops queue, dispatch unit,retirement logic.而分支預測在哪呢,他在1裡面。。。1裡面依次是ITLB,BTB,FetchDecode,Trace Cache+TC BTB+Microcode ROM,ITLB把virtual address translate成physical的,Macroinstruction被取出來後如果是branch就由BTB負責預測direction和地址。然後Macroinstruction經過Fetch Decode被翻譯成microinstruction即uops。一部分比較複雜的指令例如string move由microcode ROM負責翻譯。而Trace Cache里儲存的都是uops,他有他自己的分支預測TC BTB,這個predictor只負責預測Trace Cache uops里的跳轉指令。順便一提大部分CPU都是採用dynamic和static相結合的scheme的,如何在之間切換不同的CPU方法不一樣,P4裡面的話是lookup BTB,如果miss則啟用static。是否在解碼之後立刻就進行分支預測呢?——按照我對分支預測實現的理解,是否每段代碼,不管有沒有if分支的意思,都需要先到分支預測的buffer里轉一
圈,由處理器判斷是否有分支和跳轉呢?——還是說解碼後仍有單元會簡單判斷是否為存在分支預測的必要,隨後再送往分支預測buffer?
綜上所述,一般分支預測都是在decode之前的。現在的處理器很多都採用prefetch,這個通常不只一個pipeline stage,在指令被取出來以後會有個簡單的decode將branch區分開來送往分支預測單元。如果分支預測在最後一個stage則每次預測為taken時整個prefetch的pipeline將被flush。如果在execution時發現預測錯了,則連同後端整條pipeline都被flush。不過如今處理器分支預測的準確率不上95%真是都沒法拿出來見人啊。
把高級語言編譯成cpu能執行的二進位代碼。這個是編譯器的事,並不是cpu乾的活。
x86後期的設計的微碼器是把不等長的cisc指令解成多條等長的微碼指令(我記得cortex A15也有這個微碼器,不過作用和x86的有點不同)
cpu執行一個程序時,pc指向該程序的入口,進行逐條取指(放入流水線),當然在branch instruction之後的指令會暫時地暫停取指,等到分支預測結果出來了(分支方向和目標地址確定後)再進行後續指令的取指。
因為如果沒有分支預測功能的處理器,當分支指令取指到分支指令在EX階段得到結果的數個周期之間,沒有確定下一條指令的pc值,是無法對下一條指令進行拾取,處理器管線會產生大量氣泡,閑置硬體,嚴重影響cpu性能。所以分支預測是必須的。
所謂的預測,就是要在分支得到結果之前能準確地預測到後續指令的pc值。預測的宗旨是求快更求準確。預測的是分支方向和目標地址。
在IF階段,有一個快速解碼器對指令進行簡單的解碼,分辨出是否為分支指令。
假設inst0指令為branch指令,那麼後續的inst1,inst2....的指令就禁止被拾取進入cpu管線。把branch指令inst0放到一個分支預測器。根據inst0的pc值用(BHR/GHR)+PHT(飽和計數器)去預測inst0指令的分支方向。根據inst0的pc值用BTB/RAS去預測目標地址,某些分支指令的目標地址是以立即數的形式存在於指令中。再根據預測的目標地址和分支方向去決定下一條指令的。一般在ID階段就能預測到下一指令的pc值,之後就能根據pc值去取指。這樣看來,比(無分支預測功能)在分支指令在EX階段得到結果之後再進行讀取下一指令的處理器性能要好多了。但分支預測畢竟是預測,也有預測錯誤的時候。當發現分支預測錯誤時,在管線中位於branch指令後的指令會被抹掉。這時分支預測對處理器沒有起到任何的性能提升,還白白浪費功耗去執行那些被抹掉的指令。特別是管線長,亂序處理器的分支錯誤受到的懲罰會更大。
不過現代處理器的預測準確率還是很高的,所以分支預測對性能還是有相當的提升的。
為分支預測設計的部件硬體開銷和功耗消耗都是相當大的,尤其是預測目標地址的BTB。所以架構設計師必須在性能提升和硬體開銷取一個平衡點去設計分支預測的硬體。首先要先說的是,CPU只認識一種確定的語言,叫做機器碼(或者叫做機器指令),就是一條一條的基本操作的指令,什麼C、Java、Python一律不認識。所以,我們平時說的高級語言,一般指的是這種語言的編譯器(或解釋器),他們的作用就是,把按照某種約定的方式書寫的高級語言的代碼,轉化成機器碼,然後讓CPU執行。機器碼都是一些數字,不方便記憶,不利於編程,所以又有了一套助記符,和機器碼是一一對應的,這一套符號我們成為彙編語言。彙編語言,實際上是機器碼的人類語言表示。
取指,我們一般不說從cache中取,cache是一個對於CPU透明的一個部件,CPU執行的指令中,所有的地址表示的都是內存和寄存器的地址,他並不知道有cache這種東西存在。
指令是在內存中的,CPU要執行,當然要先把這條指令讀進來,然後分析一下這條指令表示什麼意思,然後再執行,最後寫回執行結果,一般教科書都把這個叫做取指令(IF)、分析指令(ID)、執行(EX)和寫回(WB)。
分支預測,是在流水線機裡面才有的東西。我們以剛才說的那四個過程為例,假設一個CPU執行一條指令的時候,需要這四個步驟,即IF、ID、EX和WB,而且顯然,這四個步驟每一步都有一套自己的硬體取完成相應的工作。如果CPU只執行一條執行,那麼顯然每一個時刻,都有至少有三個部件是在空閑的。所以,CPU執行部件往往拆分成好幾個部分,比如上面的四個部分。
首先先取指令,即CPU根據IP寄存器的值,到內存中把指令取回來,存在CPU的寄存器裡面;然後指令進入第二步ID,在ID處理指令的時候,為了不讓IF空閑,IF去取下一條指令回來。這樣的流水線就使得每一個部件都處於工作狀態,提高CPU的利用率。
如果遇上的跳轉指令,那麼CPU是要到EX這個步驟才能知道,跳轉是不是實際已經發生(因為跳轉的條件並不總是成立)。如果發生跳轉,這個時候,上兩級流水線預讀的指令,實際上是無效的,是不應該被執行的,所以這個時候要清空流水線,也就是,那兩條指令的取指和分析工作都白做了,然後取實際跳轉到的位置,重新開始取值、分析。顯然,清空流水線會降低CPU的運行效率。
分支預測的話,確實是有另外的硬體去處理,不過,這個預測,並不一定是完全正確的,也就是說,分支預測是會有失敗的。不過在正確率高的情況下,效率也是可觀的。CPU的指令中,能夠進行跳轉的指令並不多,就是jxx一套,還有loop之類的循環,所以只要對這些指令專門進行處理就好了。
一個比較簡單的例子,我們都知道,循環語句一般是會執行很多次的,如果我們預測循環語句的判斷條件總是成立,那麼對於一個需要循環n次的循環,那麼顯然就有n-1次預測成功,1次失敗。這樣子,這整個循環的過程,只有一次(也就是最後一次)需要清空整個流水線。如何安裝led視頻處理器,一個視視頻處理器外接多塊LED屏,怎麼連接
推薦閱讀:
※CPU 的溫度有沒有一個上限值?
※為什麼 CPU 不能直接對內存中的數據進行運算?
※CPU至強E3和酷睿i7哪個好?幫忙分析一下?
※有無必要買台高端電腦,只為幾年不換?
※預算8000上下,自己攢機?