指令亂序執行的原理【轉】


原文:https://www.techbulo.com/1963.html

處理器基本上會按照程序中書寫的機器指令的順序執行。按照書寫順序執行稱為按序執行(In-Order )。按照書寫順序執行時,如果從內存讀取數據的加載指令、除法運算指令等延遲(等待結果的時間)較長的指令后面緊跟着使用該指令結果的指令,就會陷入長時間的等待。盡管這種情況無可奈何,但有時,再下一條指令並不依賴於前面那條延遲較長的指令,只要有了操作數就能執行。

此時可以打亂機器指令的順序,就算指令位於后邊,只要可以執行,就先執行,這就是亂序執行(Out-of-Order)。

亂序執行時,由於數據依賴性而無法立即執行的指令會被延后,因此可以減輕數據災難的影響。

保留站

亂序執行采用了如圖1 所示的保留站(Reservation Station )這種類似於接待室的設施。

Reservation Station

                                                                   圖1 使用保留站亂序執行

解碼單元解碼后的指令不是直接送到流水線,而是根據各自的指令種類,將解碼后的指令送往各自的保留站中保存下來。如果操作數位於寄存器中,就把操作數從寄存器中讀出來,和指令一起放入保留站。相反,如果操作數還在由前面的指令進行計算,那么就把那條指令的識別信息保存下來。

然后,保留站把操作數齊備、可執行的指令依次送到流水線進行運算。即使指令位於前面,如果操作數沒准備好,也不能開始執行,所以保留站中的指令執行順序與程序不一致(亂序)。另外,保留站會監視執行流水線輸出的結果,如果產生的結果正好是等待中的指令的操作數,就將其讀入,這樣操作數齊備后,等待中的指令就可以執行了。

此外,圖1 給每種指令都設置了保留站,而有的處理器用一個保留站控制所有流水線。

反向依賴的問題

亂序執行在等待時間內完成其他工作,能提高效率,但也會產生問題。例如下面這段程序。

1
2
3
4
5
LD r1,[a]; ←將內存的變量a 讀入到寄存器r1(加載)
 
ADD r2,r1,r5; ←r1與r5相加,保存到r2
 
SUB r1,r5,r4; ←r5減去 r4,保存到 r1

執行這類程序時,開頭的加載指令緩存未命中時,將變量a 讀取到r1 就需要很長時間。而下一條ADD指令要使用r1 的值作為操作數,所以在加載指令完成之前,ADD指令無法執行。

但是,再下一條SUB 指令的操作數r4 和r5 的值已經求出了,利用亂序執行,無須等待前面的LD 指令、ADD指令就可以執行,但ADD指令的操作數r1 正好是后面SUB 指令保存結果的位置,因此存在反向依賴(Anti-dependency )。如圖2 所示,如果在 ADD指令之前執行SUB 指令,那么 ADD指令的操作數r1 就不再是 LD指令的結果,而變成了SUB 指令的結果,導致r2 的內容發生變化。另外,LD 指令將結果保存到r1 的行為也要比SUB 指令將結果保存到r1 的行為晚,因此后面r1 的值也會因亂序執行而變化。

Anti-dependency

                                       圖2 反向依賴的問題:在ADD指令之前執行SUB 指令時的情況

因此,存在反向依賴時,調整指令的執行順序,會導致無法得到正確結果。

重命名——消除反向依賴

為了避免反向依賴的問題,亂序執行時要進行重命名(Rename)處理。

重命名處理將程序中記載的寄存器編號(稱為“邏輯寄存器”)對應到物理寄存器編號上。各指令寫入結果的邏輯寄存器一定要分配到空閑的物理寄存器上。

1
2
3
4
5
LD p11,[a]; ←將內存的變量讀入寄存器p11 (r1)
 
ADD p12,p11,r5; ←p11 (r1)與 r5相加,保存到p12 (r2)
 
SUB p13,r5,r4; ←r5減去 r4,保存到 p13 (r1)

這樣,如圖3 所示,LD指令要將結果保存到r1 ,而實際上被重命名,結果保存到了物理寄存器p11。解碼下一條 ADD指令時,對應表中記載了 r1 = p11 ,因此將使用 r1 的部分改變為使用p11。此外,存放 ADD指令結果的r2 寄存器對應到空閑物理寄存器p12。而SUB 指令的結果也要保存到r1 ,此時要將r1對應到空閑的物理寄存器p13 上。

這樣,盡管邏輯寄存器都是r1 ,但保存LD指令結果和SUB指令結果的實際物理寄存器編號並不相同,因此即使SUB 指令比LD指令早完成,也不會發生任何問題。這種處理叫做寄存器重命名。

rename

圖3 反向依賴問題:重命名后的情況

寄存器重命名的原理

為了實現寄存器重命名,亂序執行處理器要擁有物理寄存器池,以及邏輯寄存器和物理寄存器的對應表,指令解碼時分配空閑物理寄存器,並把對應關系記錄到表中。而且,指令解碼時要查找對應表,將后續指令的操作數使用的邏輯寄存器轉換成物理寄存器。指令執行結束時,還要回收不用的物理寄存器,將其放回空閑物理寄存器池中。

此外,如果加載指令后面緊跟着另一條加載指令,在順序執行的情況下,理論上也不是不能進行流水線處理,但實際上,資源調度很困難,因此都是等待前一條加載指令完成后再執行下一條加載指令。那么,在亂序執行的情況下,只要能確定后面的加載指令的地址,就能在前一條加載指令完成后,繼續執行后面的加載指令。

如圖4 所示,亂序執行時,第 1 次內存訪問和最后一次內存訪問的處理大部分是重疊着並行執行的,因此與順序執行相比,平均內存訪問等待時間更短,與並行執行的內存訪問指令數量成反比。

當然,為了實現多個內存訪問重疊執行,內存訪問指令的處理單元必須支持流水線執行。

slolation

圖4 通過亂序執行並行執行多條加載指令

保證正確的中斷

但是,亂序執行改變指令順序后,還有可能產生問題。

1
2
3
4
5
LD r1,[a]; ←將內存的變量a 讀入到寄存器r1(加載)
 
ADD r2,r1,r5; ←r1與r5相加,保存到r2
 
SUB r3,r3,r4; ←r3減去 r4,保存到 r3

上述例子中,LD指令要訪問的變量a 的內存地址會發生頁面管理設施的TLB 未命中,進一步查找內存上的頁表,如果內存地址位於未分配給該程序的頁上時,就無法執行LD指令。

此時,處理器會發生非法訪問異常並通知操作系統。然后操作系統會執行必要的異常處理,例如為該頁分配物理內存等,並重新執行程序中的 LD指令。如果這里先把SUB 指令執行完,並將結果寫入 r3 寄存器,那么再次從LD 指令開始執行時,SUB 指令就會被執行兩次,從r3 中減去兩次r4 的值,導致r3 值發生錯誤。

但是,如果像下面這樣重命名的話:

1
2
3
4
5
LD p11,[a]; ←將內存的變量a 讀入寄存器 p11 (r1)
 
ADD p12,p11,r5; ←p11 (r1)與 r5相加,保存到p12 (r2)
 
SUB p13,r3,r4; ←r3減去 r4,保存到 p13 (r3)

那么如圖5 所示,發生異常時,就把保存LD 指令和后面的指令結果的p11、p12 、p13 與邏輯寄存器的對應關系表恢復至LD指令執行之前的狀態,就像這些指令沒執行過一樣。

irq

                                                         圖5 異常發生時恢復處理器的狀態

重命名的主要目的是消除反向依賴,增加恢復功能后,還能取消執行過的指令,正確實現中斷。

總之,重命名機制能解決反向依賴的問題,異常發生時也能保證和順序執行同樣的狀態,因此亂序執行對程序完全沒有影響。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM