CPU的流水線,分支預測與亂序執行
流水線
轉自:http://www.elecfans.com/emb/dsp/20180405657563.html
流水線的概念來源於工業製造領域,以汽車裝配為例來解釋流水線的工作方式,假設裝配一輛汽車需要四個步驟:
第一步衝壓:製作車身外殼和底盤等部件。
第二步焊接:將衝壓成形後的各部件焊接成車身。
第三步塗裝:將車身等主要部件清洗、化學處理、打磨、噴漆和烘乾。
第四步總裝:將各部件(包括髮動機和向外採購的零部件)組裝成車。
汽車裝配則同時對應需要衝壓、焊接、塗裝和總裝四個工人。最簡單的方法是一輛汽車依次經過上述四個步驟裝配完成之後,下一輛汽車才開始進行裝配,最早期的工業製造就是採用的這種原始的方式,即同一時刻只有一輛汽車在裝配。不久之後人們發現,某個時段中一輛汽車在進行裝配時,其它三個工人都處於閒置狀態,顯然這是對資源的極大浪費,於是思考出能有效利用資源的新方法,即在第一輛汽車經過沖壓進入焊接工序的時候,立刻開始進行第二輛汽車的衝壓,而不是等到第一輛汽車經過全部四個工序後才開始,這樣在後續生產中就能夠保證四個工人一直處於執行狀態,不會造成人員的閒置。這樣的生產方式就好似流水川流不息,因此被稱為流水線。
計算機體系結構教材中被提及最多的經典MIPS五級流水線如圖1所示。在此流水線中一條指令的生命週期分為:
取指:
指令取指(InstrucTIon Fetch)是指將指令從儲存器中讀取出來的過程。
譯碼:
指令譯碼(InstrucTIon Decode)是指將儲存器中取出的指令進行翻譯的過程。經過譯碼之後得到指令需要的運算元暫存器索引,可以使用此索引從通用暫存器組(Register File,Regfile)中將運算元讀出。
執行:
指令譯碼之後所需要進行的計算型別都已得知,並且已經從通用暫存器組中讀取出了所需的運算元,那麼接下來便進行指令執行(InstrucTIon Execute)。指令執行是指對指令進行真正運算的過程。譬如,如果指令是一條加法運算指令,則對運算元進行加法操作;如果是減法運算指令,則進行減法操作。
在“執行”階段的最常見部件為算術邏輯部件運算器(ArithmeTIc Logical Unit,ALU),作為實施具體運算的硬體功能單元。
訪存:
儲存器訪問指令往往是指令集中最重要的指令型別之一,訪存(Memory Access)是指儲存器訪問指令將資料從儲存器中讀出,或者寫入儲存器的過程。
寫回:
寫回(Write-Back)是指將指令執行的結果寫回通用暫存器組的過程。如果是普通運算指令,該結果值來自於“執行”階段計算的結果;如果是儲存器讀指令,該結果來自於“訪存”階段從儲存器中讀取出來的資料。
在工業製造中採用流水線可以提高單位時間的生產量,同樣在處理器中採用流水線設計也有助於提高處理器的效能。以上述的五級流水線為例,由於前一條指令在完成了“取指”進入“譯碼”階段後,下一條指令馬上就可以進入“取指”階段,依次類推,如圖2所示,如果流水線沒有停頓,理論上可以取得每個時鐘週期都完成一條指令的效能。
分支預測
轉自:https://zhuanlan.zhihu.com/p/36543235
上述流水線架構對於順序執行的命令,效果提高顯著,但是遇到跳轉命令時效率便會急劇下降,對於分支跳轉指令,我們在執行完該指令之前是不知道是否發生跳轉的,也就是說,我們在分支指令執行完之前,我們無法確定分支指令的下一條指令的地址,所以也就沒法把分支指令的下一條命令放入流水線中,只能等待分支指令執行完畢才能開始下一條命令的取指步驟,所以流水線中就會出現氣泡(Bubble),這會大大降低流水線的吞吐能力。
為了解決上述問題,分支預測器應運而生。當指令執行到分支跳轉指令時,CPU不再是空等待分支跳轉指令執行完畢給出下一條命令的地址,而是根據模型預測分支是否發生跳轉以及跳轉到哪裡,CPU將預測到的指令直接放入流水線,去執行指令的取指、譯碼等工作。
當分支跳轉指令完成執行階段後,給出是否跳轉的結果,CPU即可判斷分支跳轉預測是否正確,如果指令執行後的跳轉結果與分支預測器預測結果相一致,則流水線繼續往下執行,如果發現分支預測結果出現錯誤,則需要清空流水線,將前面不該進入流水線的指令清空,然後將正確的指令放入流水線重新執行。
亂序執行
由於在指令在一定邏輯上只能按照順序執行,比如疫情期間在家辦公,計劃的順序是起床、等快遞來送菜、做早飯、吃早飯、回覆郵件、開始工作。但是快遞小哥遲遲不來,我們們是不是得一直等著他來送菜再進行下面的事情?顯然不會,你一定會把等菜、做飯、吃飯等一系列步驟放著,先去執行另一系列的步驟(發郵件、工作),因為這兩個系列的步驟是完全不相關的。放在CPU中的邏輯時,**某條指令需要訪存,訪存的時候多級cache不命中、記憶體缺頁等原因導致訪存時間延長,這個時候是不是也像快遞小哥遲遲不來一樣,這個時候我們們就要先放棄這一系列的事情,先去執行後面的與之無關指令。**等訪存結束再去回去執行之前被耽擱的指令,這就是亂序執行(因為指令的順序被打亂了)。這樣的代價在哪裡?為什麼叫亂序執行,不叫亂序提交,那就是因為提交順序(最後結果)必須正確,比如一個暫存器按照原本的順序是 被改成3、3被訪問、被改成5、5被訪問,顯然前兩個跟後兩個沒有關係,如果出現了亂序執行,先執行了後兩個,那麼暫存器最後結果就是3了,後續跟這個暫存器打交道的指令都會出錯(原本希望最後是5),所以提交順序一定要正確,所以設計了保留站和ROB(重排序緩衝),那就是標記指令本來的執行順序,在寫回(最後一級流水線)的時候、如果比自己標記好小的指令不提交,自己就在ROB中呆著等待原本在自己前面的指令提交。這樣就保證了既節省時間又能正確提交
不光CPU會進行亂序執行,編譯器也會進行亂序執行,他們會打亂程式原本的執行順序,若不希望CPU和編譯器有如此亂序,需要自行在程式碼中加入“屏障”和“記憶體屏障”
相關文章
- CPU亂序執行基礎 —— Tomasulo演算法及執行過程演算法
- 現代中央處理器(CPU)是怎樣進行分支預測的?
- 如何在程式碼層面提供CPU分支預測效率
- cpu、核與執行緒執行緒
- 併發程式設計與高併發解決方案學習(CPU多級快取-亂序執行優化)程式設計快取優化
- Unity 渲染流水線 :CPU與GPU合作創造的藝術wfdUnityGPU
- 分支預測:為什麼有序陣列比無序陣列快?陣列
- 從一段 Dubbo 原始碼到 CPU 分支預測的一次探險之旅原始碼
- 自定義xunit測試用例的執行順序
- pytest(4)-測試用例執行順序
- pipeline的執行順序
- return與finally的執行順序的影響(skycto JEEditor)
- [原始碼解析] 深度學習流水線並行 PipeDream(4)--- 執行時引擎原始碼深度學習並行
- 【胡思亂想】JNI與執行緒池的維護執行緒
- 經典問題之「分支預測」
- Spring Aop的執行順序Spring
- JAVA CPU100%與執行緒死鎖定位Java執行緒
- mySQL 執行語句執行順序MySql
- 對多執行緒程式,單核cpu與多核cpu如何工作相關的探討執行緒單核
- Sql執行順序SQL
- mysql 中sql語句關鍵字的書寫順序與執行順序MySql
- 進擊谷歌:多執行緒下程式順序怎麼穩定不亂?谷歌執行緒
- promise、async、await非同步原理與執行順序PromiseAI非同步
- 測試平臺-unittest 指定順序執行用例
- 亂序的兩種方法
- SQL 語句的執行順序SQL
- mysql 語句的執行順序MySql
- 關於 Promise 的執行順序Promise
- Java中,類與類,類中的程式碼執行順序Java
- 聊聊如何讓springboot攔截器的執行順序按我們想要的順序執行Spring Boot
- Java執行緒的CPU時間片Java執行緒
- Pytest 順序執行,依賴執行,引數化執行
- JavaScript執行順序分析JavaScript
- sql語句執行順序與效能優化(1)SQL優化
- 同步任務與非同步任務執行順序非同步
- 壓測時介面都按照順序去執行對嗎?
- 一文搞懂Python Unittest測試方法執行順序Python
- [原始碼解析] 深度學習流水線並行Gpipe(1)---流水線基本實現原始碼深度學習並行