Textbook:
《計算機組成與設計——硬件/軟件接口》 HI
《計算機體系結構——量化研究方法》 QR
在前面一節里我們有了一塊簡單的RISC CPU,包括指令集和各個部件。現在我們來看看怎么在它的基礎上構建一個pipeline
pipeline
pipeline的概念本科的時候其實學過了...大意就是把一整個部件(可以理解成電路)分解成多個stage,這樣不同stage之間就可以並行的執行不同指令了。
PPT P1-P5 / HI P183
Pipeline Hazards
HI P186
流水線有這樣一種情況:在下一個時鍾周期中下一條指令不能執行。這種情況叫做冒險。流水線冒險包括以下三種:
1. 結構冒險(structural hazard) PPT P6
如果有兩個不同的stage需要訪問同一個資源,那它倆就不能並行運行了,這時候有的就只能wait。這叫結構冒險
一個好點的解決方法就是create duplicate resource
2. 數據冒險(data hazard) PPT P7 HI P188
因無法提供指令執行所需數據而導致指令不能在預定的時鍾周期內執行。也就是第二條指令需要第一條指令完成后才能運行(比如需要第一步算出來的結果)。這個也叫做data dependence
有三種data dependence:
- Read after Write
- Write after Read(比如 r4=r1+r0 和 r0=r3+4)
- Write after Write(2個指令write the same register)
只有第一種是true dependence,別的都可以通過一些方法解決(比如duplicate resource之類)。
對於Read after Write,就只能確保read指令必須要在write完成之后才能開始。對於存在這種依賴關系的指令,直接按照pipeline的方法執行肯定是不行的。以下面的例子為例:
add $s0, $t0, $t1 //s0:=t0+t1 sub $t2, $s0, $t3 //t2:=s0-t3
這時在兩條語句執行的中間就需要Stalling。也就是delay instruction until hazard is resolved。stall的實現是通過對不同部件發送以下三種control signal來實現的:
- “Transfer” (normal operation) indicates should transfer next state to current
- “Stall” indicates that current state should not be changed
- “Bubble” indicates that current state should be set to 0
eg: PPT P8
但是當流水線里的bubble很多的時候,stalling會大幅度降低性能...所以還要用一些別的黑科技來盡量避免stalling
- 1. Forwarding(旁路) PPT P9-13 / HI P187
在解決數據冒險問題之前不需要等待指令的執行結束。對於上述的代碼序列, 一旦ALU生成了加法運算的結果,就可以將它用作減法運算的一個輸入項。從內部資源中直接提前得到缺少的運算項。例如在這個問題中,ALU data generated at end of EX; consumed at beginning of EX。那么就可以在兩個指令的EX階段之間加個旁路:
同理,有時候依賴是這樣的:第一條指令執行到memory stage了(已經過了EX,顯然運算結果已經有了),而此時后面的某一條指令要依賴這個結果。那么也可以在MEM之后到EX之前加個旁路:
而對於一些別的指令,可能還需要加別的旁路。比如下面的例子: PPT P12
1: $Ra <-- Mem[$Rb +offset] //Load
2: Mem[$Rb +offset] <-- $Ra //Store
它的流水線就簡潔一些了,只需要ID(Instruction decode/register fetch)、MEM(讀寫memory)、WB(寫register)三個stage。而在這兩條指令之間,1的MEM結束之后其實就已經得到數據了,而2恰好也是在MEM階段開始前需要數據。因此可以在 MEM的輸出 到 MEM的輸入 再加個旁路。
其實對於ALU和Load/Store指令,有這三種旁路就夠了:
3. 控制冒險(control hazard) PPT P13-25
Conditional branches cause uncertainty to instruction sequencing. 這樣只有conditional branch算出來之后才能fetch next instruction。
對於Branch Instructions,zero testing是在EX階段進行的,因此也是在這個階段需要data。其他的分析都和前面一樣了 PPT P13-14
而有一種Branch Instructions是會修改PC的(比如滿足條件時jump到某個指令),這時候問題就來了: PPT P16-21 / HI P215 ch4.8.1
0x0: beq r31, 0x18 # Take: if($r31==0) GOTO 0x18 0x4: r1 <- r31 + 0x3f # Xtra1 0x8: r2 <- r31 + 0x3f # Xtra2 0xc: r3 <- r31 + 0x3f # Xtra3 0x10: r4 <- r31 + 0x3f # Xtra4 ...... 0x18: r5 <- r31 + 0x3f # Target
按照pipeline的方式執行,0x0的beq指令要經過IF ID EX MEM WB五步。那么到beq算出來要不要跳轉(EX之后)的時候,后面的Xtra1,Xtra2,Xtra3三個指令也已經在流水線中了。假設此時需要跳轉,那么這三條指令就白執行了。對於這里的情況還好,如果Xtra1,Xtra2,Xtra3中有些涉及到寫內存的指令,那么已經造成的操作可能就很難復原了......一種方法是當發現branch instruction之后,直接在后面插入2個stall。那么在beq的EX之后,pipeline中就只有fetch Xtra1在執行了。如果要跳轉,那就清空流水線重新fetch Target(相當於一共stall了三次);如果不用跳轉,就按原計划執行(總共stall兩次)。 [PPT P18-20]
經過計算可以發現,這種方法帶來的額外時間消耗還是挺多的......(PPT P21)。所以還要想想別的方法。
- 1. Fetch and cancel When Taken
意思就是假設分支不發生,讓流水線按順序往下fetch。如果后來發現真的要branch了,就清空流水線(cancel instructions),相當於插入了三個bubble。如果沒branch就按原計划執行,無bubble。
但是這個性能還是不大行的[PPT P24]。而且前面提到過,也不是所有指令都能輕易cancel的...
Scalar Pipeline
PPT P25
前面實現了一個基本的pipeline(scalar pipeline),但它還是有幾個缺點:IPC最大只有1、long latency、有時候要stall。有兩種改進方案:
1. 超流水線(superpipelining):更多的stage
超級流水線(Super Pipeline) 超級流水線又叫做深度流水線,它是提高cpu速度通常采取的一種技術。CPU處理指令是通過Clock來驅動的,每個clock完成一級流水線操作。每個周期所做的操作越少,那么需要的時間久越短,時間越短,頻率就可以提得越高。所以超級流水線就是將cpu處理指令的操作進一步細分,增加流水線級數來提高頻率。頻率高了,當流水線開足馬力運行時平均每個周期完成一條指令(單發射情況下),這樣cpu處理得速度久提高了。
當然,這是理想情況下,一般是流水線級數越多,重疊執行的執行就越多,那么發生競爭沖突得可能性就越大,對流水線性能有一定影響。 現在很多cpu都是將超標量和超級流水線技術一起使用,例如pentium IV,流水線達到20級,頻率最快已經超過3GHZ.我們教科書上用於教學的經典MIPS只有5級流水。
2. 超標量(superscalar):一次fetch多條指令
超標量(Super Scalar) 將一條指令分成若干個周期處理以達到多條指令重疊處理,從而提高cpu部件利用率的技術叫做標量流水技術。 超級標量是指cpu內一般能有多條流水線,借助硬件資源重復(例如有兩套譯碼器和ALU等)來實現空間的並行操作。在單流水線結構中,指令雖然能夠重疊執行,但仍然是順序的,每個周期只能發射(issue)或退休(retire)一條指令。
超級標量結構的cpu支持指令級並行,每個周期可以發射多條指令(2-4條居多,也叫做多發射[multiple issue])。這樣可以使得cpu的IPC(Instruction Per Clock)>1 (也就是CPI<1咯),從而提高cpu處理速度。超級標量機能同時對若干條指令進行譯碼,將可以並行執行的指令送往不同的執行部件(也就是說執行過程可以是亂序的)。我們熟知的pentium系列(可能是p-II開始),還有SUNSPARC系列的較高級型號,以及MIPS若干型號等都采用了超級標量技術。
指令級並行是一個很重要的研究方向。下一節我們就來看看如何實現指令級並行吧!
...