流水線讓CPU同時(shí)執(zhí)行多條指令,但指令之間會(huì)打架。結(jié)構(gòu)冒險(xiǎn)搶硬件,數(shù)據(jù)冒險(xiǎn)搶數(shù)據(jù),控制冒險(xiǎn)搶方向。這篇聊聊這三種冒險(xiǎn)的本質(zhì)和應(yīng)對(duì)方法。
1. 結(jié)構(gòu)冒險(xiǎn):硬件不夠用了
1.1 什么是結(jié)構(gòu)冒險(xiǎn)
多條指令同時(shí)需要同一個(gè)硬件資源[1]。比如:
- 兩條指令都要寫寄存器,但寄存器堆只有一個(gè)寫端口
- 兩條指令都要用除法器,但除法器只有一個(gè)
- 取指和訪存同時(shí)訪問內(nèi)存,但只有一個(gè)內(nèi)存端口
1.2 MIPS浮點(diǎn)流水線的例子
MIPS的浮點(diǎn)單元有多個(gè)功能部件[2]:
- FP Adder:3-4周期
- FP Multiplier:6-7周期
- FP Divider:24周期(非流水化)
寄存器寫端口沖突:
Cycle 11:
ADD.D F2, F4, F6 ; 完成,要寫F2
MUL.D F8, F10, F12 ; 完成,要寫F8
L.D F14, 0(R2) ; 完成,要寫F14
三個(gè)指令同時(shí)到達(dá)WB階段,但FP寄存器堆只有一個(gè)寫端口[2]。這就是結(jié)構(gòu)冒險(xiǎn)。
解決方案:
- 增加寫端口:成本高,利用率低(大部分時(shí)間只有一個(gè)寫)
- 檢測(cè)并停頓:在ID階段檢測(cè)沖突,延遲發(fā)射
- 移位寄存器跟蹤:記錄未來哪些周期會(huì)寫寄存器,提前沖突檢測(cè)
1.3 除法單元的獨(dú)占
MIPS的FDIV需要24周期,且非流水化[2]:
Cycle 1: FDIV.D F1, F2, F3 ; 開始除法
Cycle 2-24: 其他指令執(zhí)行,但第二個(gè)FDIV必須等
Cycle 25: FDIV.D F4, F5, F6 ; 下一個(gè)除法才能開始
這是結(jié)構(gòu)冒險(xiǎn)的極端情況——功能部件被長(zhǎng)期占用。
解決方案:
- 流水化除法器:把24周期拆成多級(jí),每級(jí)可重疊
- 增加除法單元:成本高,除法不頻繁
- 編譯器調(diào)度:在除法后面插入無關(guān)指令
2. 數(shù)據(jù)冒險(xiǎn):數(shù)據(jù)還沒準(zhǔn)備好
2.1 RAW(Read After Write)
最普遍的冒險(xiǎn)。后一條指令讀前一條指令要寫的結(jié)果,但還沒寫好。
L.D F4, 0(R2) ; 加載數(shù)據(jù)到F4
MUL.D F0, F4, F6 ; 用F4做乘法
L.D在MEM階段才拿到數(shù)據(jù),MUL.D在EX階段就需要F4。即使轉(zhuǎn)發(fā),也需要停頓1周期[3]。
轉(zhuǎn)發(fā)(Forwarding/Bypassing)[3][4][5]:
- 把ALU結(jié)果直接傳給下一條指令的EX輸入
- 把MEM結(jié)果傳給EX輸入
- 減少但無法完全消除停頓
Load-Use Hazard的特殊性[3]:
LW x1, 0(x2) ; 加載
ADD x3, x1, x1 ; 立即使用x1
Load數(shù)據(jù)在MEM階段末尾才可用,ADD在EX階段就需要。轉(zhuǎn)發(fā)無法解決(不能向過去轉(zhuǎn)發(fā)),必須停頓1周期或插入NOP[3]。
2.2 WAW(Write After Write)
兩條指令寫同一個(gè)寄存器,可能亂序完成導(dǎo)致錯(cuò)誤結(jié)果。
MIPS浮點(diǎn)例子[2]:
ADD.D F2, F4, F6 ; 延遲3周期
L.D F2, 0(R2) ; 延遲1周期
L.D比ADD.D快,可能先寫F2,然后ADD.D覆蓋它。程序期望的是ADD.D的結(jié)果。
解決方案:
2.3 WAR(Write After Read)
后一條指令寫前一條指令要讀的寄存器。但在流水線中,讀總是在ID階段,寫在WB階段,所以WAR不會(huì)自然發(fā)生[3]。
只有在亂序執(zhí)行且讀延后的情況下才可能發(fā)生,現(xiàn)代CPU通過寄存器重命名消除[7][8]。
3. 控制冒險(xiǎn):分支走哪條路
3.1 分支延遲
5級(jí)流水線中,分支在EX階段才確定目標(biāo)地址:
Cycle 1: BEQ taken? (EX階段計(jì)算)
Cycle 2: 如果預(yù)測(cè)錯(cuò)誤,已取兩條錯(cuò)誤指令
分支預(yù)測(cè)失敗的代價(jià):
3.2 預(yù)測(cè)與恢復(fù)
靜態(tài)預(yù)測(cè):總是預(yù)測(cè)不跳轉(zhuǎn),或編譯器標(biāo)記[10]
動(dòng)態(tài)預(yù)測(cè):基于歷史記錄預(yù)測(cè)[10]
- Bimodal:簡(jiǎn)單的2位飽和計(jì)數(shù)器
- Global:考慮全局分支歷史
- Local:考慮每個(gè)分支的歷史
恢復(fù)機(jī)制:
3.3 精確異常
異常發(fā)生時(shí),處理器狀態(tài)必須與程序順序一致[6][7][8]。ROB保證:
- 異常指令前的所有指令已提交
- 異常指令后的指令未修改狀態(tài)
- 可以正確恢復(fù)并跳轉(zhuǎn)到異常處理程序
4. 三類冒險(xiǎn)的對(duì)比
| 冒險(xiǎn)類型 | 原因 | 檢測(cè)階段 | 解決方案 | 代價(jià) |
|---|---|---|---|---|
| 結(jié)構(gòu)冒險(xiǎn) | 資源爭(zhēng)用 | ID | 增加資源/停頓調(diào)度 | 面積/性能 |
| RAW | 數(shù)據(jù)未就緒 | ID/EX | 轉(zhuǎn)發(fā)/停頓/亂序執(zhí)行 | 邏輯復(fù)雜度 |
| WAW | 亂序?qū)懟?/td> | ID | ROB順序提交/重命名 | 硬件復(fù)雜度 |
| 控制冒險(xiǎn) | 分支方向 | EX | 預(yù)測(cè)/延遲槽/ROB | 預(yù)測(cè)失敗懲罰 |
5. 現(xiàn)代處理器的應(yīng)對(duì)
5.1 亂序執(zhí)行(Out-of-Order)
- 指令順序發(fā)射(Issue)
- 亂序執(zhí)行(Execute)
- 順序提交(Commit)
消除WAW/WAR,隱藏RAW延遲。
5.2 推測(cè)執(zhí)行(Speculative Execution)
分支預(yù)測(cè)后,提前執(zhí)行后續(xù)指令[7]:
- 預(yù)測(cè)正確:加速執(zhí)行
- 預(yù)測(cè)錯(cuò)誤:ROB丟棄結(jié)果,恢復(fù)狀態(tài)
5.3 轉(zhuǎn)發(fā)網(wǎng)絡(luò)
多級(jí)轉(zhuǎn)發(fā)覆蓋各種依賴[3][11]:
- EX→EX:ALU結(jié)果直接轉(zhuǎn)發(fā)
- MEM→EX:Load數(shù)據(jù)轉(zhuǎn)發(fā)
- WB→EX:寫回階段轉(zhuǎn)發(fā)
6. 總結(jié)
流水線冒險(xiǎn)的本質(zhì)是并行執(zhí)行與程序順序的沖突。
結(jié)構(gòu)冒險(xiǎn):硬件資源有限,需要復(fù)制或調(diào)度
數(shù)據(jù)冒險(xiǎn):數(shù)據(jù)依賴存在,需要轉(zhuǎn)發(fā)或等待
控制冒險(xiǎn):程序流程不確定,需要預(yù)測(cè)和恢復(fù)
現(xiàn)代CPU通過亂序執(zhí)行+ROB+寄存器重命名+分支預(yù)測(cè)的組合拳,把冒險(xiǎn)的影響降到最低。但這些機(jī)制增加了硬件復(fù)雜度和功耗,是性能與成本的永恒權(quán)衡。
參考
-
GeeksforGeeks. Pipeline Hazards. ?
-
Jyoti Prakash Blog. Delving Deeper into the MIPS Pipeline. FP pipeline hazards. ? ? ? ? ?
-
Chipmunk Logic. Designing RISC-V CPU from scratch – Part 3. Pipeline hazards. ? ? ? ? ? ? ?
-
Lori Academic Direct. Data hazards examples. ?
-
Imperial College London. EIE2 Instruction Architectures & Compilers Lecture 8. ?
-
IEEE. Characterizing the branch misprediction penalty. ?
-
UPC Commons. Bypassing in pipelines. ?