CPU的流水線,分支預測與亂序執行

alex_mist發表於2020-11-18

流水線

轉自: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和編譯器有如此亂序,需要自行在程式碼中加入“屏障”和“記憶體屏障”

相關文章