CPU是如何解決冒險問題的?
想通過流水線設計來提升CPU的吞吐率,我們需要冒哪些風險。
流水線設計需解決的三大冒險:
結構冒險(Structural Hazard)
數據冒險(Data Hazard)
控制冒險(Control Hazard)
CPU流水線設計里,會遇到各種“危險”,使得流水線的下一條指令不能正常運行。但還是通過“搶跑”,“冒險”拿到一個提升指令吞吐率的機會。
流水線架構的CPU,是主動進行的冒險選擇。期望能夠通過冒險帶來更高回報,所以,這不是無奈之下的應對之舉,自然也算不上什么危機。
對于各種冒險可能造成的問題,其實都準備好了應對方案。
結構冒險
本質上是一個硬件層面的資源競爭問題,即一個硬件電路層面的問題。
CPU在同一個時鐘周期,同時在運行兩條計算機指令的不同階段。但是這兩個不同的階段,可能會用到同樣的硬件電路。
最典型的例子就是內存數據訪問。
同一個時鐘周期,兩個不同指令訪問同一個資源(5級流水線的示意圖)
第1條指令執行到訪存(MEM)時,流水線第4條指令,在執行取指令(Fetch)的操作。訪存和取指令,都要進行內存數據的讀取。內存只有一個地址譯碼器的作為地址輸入,那就只能在一個時鐘周期里讀取一條數據,沒法同時執行第1條指令的讀取內存數據和第4條指令的讀取指令代碼。
類似的資源沖突最常見的就是薄膜鍵盤“鎖鍵”。
薄膜鍵盤不是每一個按鍵背后都有獨立線路,而是多個鍵共用一個線路。如果在同一時間,按下兩個共用一個線路的按鍵,這兩個按鍵信號就沒法都傳輸出去。
重度鍵盤用戶,都要買機械鍵盤或電容鍵盤。因為按鍵都有獨立傳輸線路,“全鍵無沖”,大量寫文章、寫程序,還是打游戲,都不會按下鍵卻沒生效。
“全鍵無沖”本質就是增加資源。同樣可用在CPU結構冒險。
對訪問內存數據和取指令的沖突,把我們的內存分成兩部分,各有各的地址譯碼器。這兩部分分別是存放指令的程序內存和存放數據的數據內存。
這樣把內存拆成兩部分的解決方案,在計算機體系結構里叫作哈佛架構(Harvard Architecture)。
馮·諾依曼體系結構,又叫作普林斯頓架構(Princeton Architecture)。
如今的CPU仍是馮·諾依曼體系結構,并未將內存拆成程序內存、數據內存。
因為那樣拆分,對程序指令和數據需要的內存空間,就無法根據實際應用去動態分配。雖然解決了資源沖突,但也失去靈活性。
現代CPU架構,借鑒了哈佛架構,在高速緩存層面拆分成指令緩存和數據緩存
不過,借鑒了哈佛結構的思路,現代的CPU雖然沒有在內存層面進行對應的拆分,卻在CPU內部的高速緩存部分進行了區分,把高速緩存分成了指令緩存(Instruction Cache)和數據緩存(Data Cache)兩部分。
內存的訪問速度遠比CPU的速度要慢,所以現代的CPU并不會直接讀取主內存。它會從主內存把指令和數據加載到高速緩存中,這樣后續的訪問都是訪問高速緩存。而指令緩存和數據緩存的拆分,使得我們的CPU在進行數據訪問和取指令的時候,不會再發生資源沖突的問題了。
結構冒險是一個硬件層面的問題,我們可以靠增加硬件資源的方式來解決。然而還有很多冒險問題,是程序邏輯層面的事兒。其中,最常見的就是數據冒險。
數據冒險:三種不同的依賴關系
同時在執行的多個指令之間,有數據依賴。
這些數據依賴,可分成三類:
先寫后讀(Read After Write,RAW)
先讀后寫(Write After Read,WAR)
寫后再寫(Write After Write,WAW)
先寫后讀(Read After Write)
C語言代碼編譯出來的匯編指令。
int main() { int a = 1; int b = 2; a = a + 2; b = a + 3; } int main() { 0: 55 push rbp 1: 48 89 e5 mov rbp,rsp int a = 1; 4: c7 45 fc 01 00 00 00 mov DWORD PTR [rbp-0x4],0x1 int b = 2; b: c7 45 f8 02 00 00 00 mov DWORD PTR [rbp-0x8],0x2 a = a + 2; 12: 83 45 fc 02 add DWORD PTR [rbp-0x4],0x2 b = a + 3; 16: 8b 45 fc mov eax,DWORD PTR [rbp-0x4] 19: 83 c0 03 add eax,0x3 1c: 89 45 f8 mov DWORD PTR [rbp-0x8],eax } 1f: 5d pop rbp 20: c3 ret
內存地址為12的機器碼,把0x2添加到 rbp-0x4 對應內存地址
內存地址為16的機器碼,又要從rbp-0x4內存地址,把數據寫入eax寄存器。
所以,需要保證內存地址為16的指令讀取rbp-0x4的值前,內存地址12的指令寫入到rbp-0x4的操作必須完成。
這就是先寫后讀所面臨的數據依賴。這順序保證不了,程序就是錯的!
這種先寫后讀的依賴關系稱為數據依賴,Data Dependency。
先讀后寫(Write After Read)
這次我們先計算 a = b + a,然后再計算 b = a + b。
int main() { int a = 1; int b = 2; a = b + a; b = a + b; } int main() { 0: 55 push rbp 1: 48 89 e5 mov rbp,rsp int a = 1; 4: c7 45 fc 01 00 00 00 mov DWORD PTR [rbp-0x4],0x1 int b = 2; b: c7 45 f8 02 00 00 00 mov DWORD PTR [rbp-0x8],0x2 a = b + a; 12: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8] 15: 01 45 fc add DWORD PTR [rbp-0x4],eax b = a + b; 18: 8b 45 fc mov eax,DWORD PTR [rbp-0x4] 1b: 01 45 f8 add DWORD PTR [rbp-0x8],eax } 1e: 5d pop rbp 1f: c3 ret
內存地址為15的匯編指令里,要把 eax 寄存器值讀出,加到 rbp-0x4 的內存地址里。
在內存地址為18的匯編指令里,再寫入更新 eax 寄存器里面。
如果在內存地址18的eax的寫入先完成了,在內存地址為15的代碼里面取出 eax 才發生,程序計算就錯。這里,我們同樣要保障對于eax的先讀后寫的操作順序。
這個先讀后寫的依賴,一般被叫作反依賴,Anti-Dependency。
寫后再寫(Write After Write)
先設置變量 a = 1,再設置變量 a = 2。
int main() { int a = 1; a = 2; } int main() { 0: 55 push rbp 1: 48 89 e5 mov rbp,rsp int a = 1; 4: c7 45 fc 01 00 00 00 mov DWORD PTR [rbp-0x4],0x1 a = 2; b: c7 45 fc 02 00 00 00 mov DWORD PTR [rbp-0x4],0x2 }
內存地址4所在的指令和內存地址b所在的指令,都是將對應的數據寫入到 rbp-0x4 的內存地址里面。
如果內存地址b的指令在內存地址4的指令之后寫入。那么這些指令完成之后,rbp-0x4 里的數據就是錯誤的。這就會導致后續需要使用這個內存地址里的數據指令,沒有辦法拿到正確的值。
所以,也需要保障內存地址4的指令的寫入,在內存地址b的指令的寫入之前完成。
這個寫后再寫的依賴,叫輸出依賴,Output Dependency。
流水線停頓
除了讀之后再進行讀,對同一寄存器或內存地址的操作,都有明確強制順序。而這個順序操作的要求,也為使用流水線帶來挑戰。
因為流水線架構的核心,就是在前一個指令還沒有結束時,后面的指令就要開始執行。
所以,需要有解決這些數據冒險的辦法。
最簡單也是最笨的就是流水線停頓(Pipeline Stall),或流水線冒泡(Pipeline Bubbling)。
若發現后面執行的指令,會對前面執行的指令有數據層面的依賴關系,就“再等等”。
進行指令譯碼時,會拿到對應指令所需訪問的寄存器和內存地址,這時就能判斷這個指令是否會觸發數據冒險。
會觸發,就能決定讓整個流水線停頓一或者多周期。
時鐘信號會不停地在0、1之間自動切換。所以,其實沒法真停頓,流水線的每個操作步驟必須要干點事。
所以,實際上并非讓流水線真停下來,而是在執行后續操作步驟前,插入一個NOP操作,即執行一個只負責摸魚的操作。
這插入的指令,就好像一個水管(Pipeline)里進了個空氣泡。在水流經過時,并沒有真的傳送水到下一個步驟,而是給了個啥都沒有的空氣泡,因此得名流水線冒泡(Pipeline Bubble)。
總結
可通過增加資源解決結構冒險問題。
現代CPU體系結構,也是在馮·諾依曼體系結構下,借鑒哈佛結構的一個混合結構解決方案。內存雖然沒有按功能拆分,但在高速緩存層面拆分成指令緩存和數據緩存,從硬件層面,使得同一個時鐘下對于相同資源的競爭不再發生。
也可通過“等待”,即插入NOP操作解決冒險問題,即流水線停頓。
不過,流水線停頓這樣的解決方案要犧牲CPU性能。因為,實際上在最差的情況下,我們的流水線架構的CPU,又會退化成單指令周期的CPU。
參考
《計算機組成與設計:硬件/軟件接口》的第4.5~4.7章
匯編語言
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。