<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>

新聞中心

EEPW首頁(yè) > 模擬技術(shù) > 設計應用 > 探秘X86架構CPU流水線(xiàn)

探秘X86架構CPU流水線(xiàn)

作者: 時(shí)間:2018-08-15 來(lái)源:網(wǎng)絡(luò ) 收藏

本文引用地址:http://dyxdggzs.com/article/201808/386844.htm

第一個(gè)改變是指令從內存中取到處理器的指令緩存的過(guò)程?,F代處理器能夠檢測何時(shí)會(huì )產(chǎn)生一個(gè)大的分支跳轉(比如函數調用),然后提前將跳轉目的地的指令加載到指令緩存中。

譯碼級有一些略微的修改。不同于以往處理器僅僅譯碼指令指針指向的指令,奔騰 Pro 處理器每一個(gè)時(shí)鐘周期最多能譯碼 3 條指令?,F今的處理器(2008-2013 年)每個(gè)時(shí)鐘周期最多可以譯碼 4 條指令。譯碼過(guò)程產(chǎn)生很多小片的操作,被稱(chēng)作微指令(micro-ops, ?-ops)。

下一級(或者好幾級)被稱(chēng)為微指令翻譯,接著(zhù)是寄存器重命名(register aliasing)。許多操作同時(shí)執行,并且執行的順序是亂序的,所以有可能出現一條指令讀一個(gè)寄存器的同時(shí),另外一條指令正在對這個(gè)寄存器進(jìn)行寫(xiě)操作。在處理器內部,這些原始的寄存器(如 AX,BX,CX,DX 等)被翻譯(或者重命名)成為內部的寄存器,而這些寄存器對程序員是不可見(jiàn)的。寄存器和內存地址需要被映射到一個(gè)臨時(shí)的地方用于指令執行。當前每個(gè)始終周期可以翻譯 4 條微指令。

當微指令翻譯完成后,它們會(huì )進(jìn)入一個(gè)重排序緩存(Reorder Buffer, ROB),ROB 可以存儲最多 128 條微指令。在支持超線(xiàn)程的處理器上,ROB 同樣可以重排來(lái)自?xún)蓚€(gè)虛擬處理器的指令。兩個(gè)虛擬處理器在 ROB 中將微指令匯集到一個(gè)共享的亂序執行部件中。

這些微指令已經(jīng)準備好可以執行了。它們被放在保留站中(Reservation Station, RS)。RS 最多可以同時(shí)存儲 36 條微指令。

現在才開(kāi)始亂序執行部件神奇的部分。不同的微指令在不同的執行單元中同時(shí)執行,而且每個(gè)執行單元都全速運行。只要當前微指令所需要的數據就緒,而且有空閑的執行單元,微指令就可以立即執行,有時(shí)甚至可以跳過(guò)前面還未就緒的微指令。通過(guò)這種方式,需要長(cháng)時(shí)間運行的操作不會(huì )阻塞后面的操作,流水線(xiàn)阻塞帶來(lái)的損失被極大的減小了。

奔騰 Pro 的亂序執行部件擁有 6 個(gè)執行單元:兩個(gè)定點(diǎn)處理單元,一個(gè)浮點(diǎn)處理單元,一個(gè)取數單元,一個(gè)存地址單元,一個(gè)存數單元。這兩個(gè)定點(diǎn)處理單元有所不同,一個(gè)能夠處理復雜定點(diǎn)操作,一個(gè)能同時(shí)處理兩個(gè)簡(jiǎn)單操作。在理想狀況下,奔騰 Pro 的亂序執行部件可以在一個(gè)時(shí)鐘周期內執行 7 條微指令。

現今的亂序執行部件仍然擁有 6 個(gè)執行單元。其中取數單元,存地址單元,存數單元沒(méi)有變,另外 3 個(gè)多少發(fā)生了變化。這三個(gè)執行單元都可以執行基本算術(shù)運算,或者執行更復雜的微指令。但每個(gè)執行單元擅長(cháng)執行不同種類(lèi)的微指令,使得它們能更高效的執行運算。在理想狀況下,現今的亂序執行部件可以在一個(gè)時(shí)鐘周期內執行 11 條微指令。

最終微指令會(huì )得到執行,在經(jīng)過(guò)數個(gè)流水級之后,最終會(huì )退出流水線(xiàn)。這時(shí),這條指令完成并且遞增指令指針。但從程序員的角度來(lái)說(shuō),指令僅僅是從一端進(jìn)入 CPU,從另一端退出,就像老的 8086 一樣。

如果你仔細看過(guò)上面的內容,你會(huì )注意到上面提到過(guò)很重要的一個(gè)問(wèn)題:如果執行指令的位置發(fā)生了跳轉會(huì )發(fā)生什么?例如,當指令運行到“if”或者是“switch”時(shí),會(huì )發(fā)生什么呢?在較老的處理器中這意味著(zhù)清空流水線(xiàn),等待新的跳轉目的指令的取指執行。

當 CPU 指令隊列中存儲了超過(guò) 100 條指令時(shí),發(fā)生流水線(xiàn)阻塞帶來(lái)的性能損失是極其嚴重的。所有的指令都需要等待跳轉目的的指令取回并且重啟流水線(xiàn)。在這種情況下,亂序執行部件需要將跳轉指令之后但是已經(jīng)執行的微指令全部取消掉,返回到執行前的狀態(tài)。當所有亂序執行的微指令都退出亂序執行部件之后,將它們丟棄掉,然后從新的地址開(kāi)始執行。這對于處理器來(lái)說(shuō)是相當困難的,而且發(fā)生的頻率很高,因此對性能的影響很大。這時(shí),引入了亂序執行部件的另外一個(gè)重要功能。

答案就是猜測執行。猜測執行意味著(zhù)當遇到一個(gè)分支指令后,亂序執行部件會(huì )將所有分支的指令都執行一遍。一旦分支指令的跳轉方向確定后,錯誤跳轉方向的指令都將被丟棄。通過(guò)同時(shí)執行兩個(gè)跳轉方向的指令,避免了由于分支跳轉導致的阻塞。處理器設計者還發(fā)明了分支預測緩存,當面臨多個(gè)分支時(shí)進(jìn)行預測,進(jìn)一步提高了性能。雖然 CPU 阻塞仍然會(huì )發(fā)生,但是這個(gè)解決方案將 CPU 發(fā)生阻塞的概率降到了一個(gè)可以接受的范圍。

最后,擁有超線(xiàn)程的處理器將兩個(gè)虛擬的處理器暴露給共享的亂序執行部件。它們共享一個(gè)重排序緩存和亂序執行部件,讓操作系統認為它們是兩個(gè)獨立的處理器,看上去就像這樣:

超線(xiàn)程的處理器擁有兩個(gè)虛擬的處理器,從而可以給亂序執行部件提供更多的數據。超線(xiàn)程對一般的應用程序都有性能提升,但是對一些計算密集型的應用,則會(huì )迅速使得亂序執行部件飽和。在這種情況下,超線(xiàn)程反而會(huì )略微降低性能。但這種情況畢竟是少數,超線(xiàn)程對于日常應用來(lái)講通常都能夠提供大約一倍的性能。

一個(gè)示例

這一切看上去有點(diǎn)令人感到困惑,那么我們舉一個(gè)例子來(lái)讓這一切變得清晰起來(lái)。

從應用程序的角度來(lái)看,我們仍然是運行在指令流水線(xiàn)上,就想老的 8086 處理器那樣。處理器就是一個(gè)黑盒子。黑盒子會(huì )處理指令指針指向的指令,當處理完之后,會(huì )在內存里找到處理的結果。

但是從指令本身的角度來(lái)講,這個(gè)過(guò)程可謂歷經(jīng)滄桑。我們下面介紹對于現今的處理器(大約在 2008-2013 年之間),一條指令在其內部的過(guò)程。

首先,你是一條指令,你所屬的程序正在運行。

你一直在耐心的等待指令指針會(huì )指向自己,等待被 CPU 運行。當指令指針距離你還有 4KB 遠的時(shí)候(這大約是 1500 條指令),你被 CPU 從內存取到指令緩存中。雖然從內存加載進(jìn)入指令緩存需要一段時(shí)間,但是現在距離你被執行的時(shí)刻還很遠,你有足夠的時(shí)間。這個(gè)預取的過(guò)程屬于流水線(xiàn)的第一級。

當指令指針離你越來(lái)越近,距離你還有 24 條指令的時(shí)候,你和你旁邊的 5 個(gè)指令會(huì )被放到指令隊列里面。

這個(gè)處理器有 4 個(gè)譯碼器,可以容納一個(gè)復雜指令和最多三個(gè)簡(jiǎn)單指令。你碰巧是一條復雜指令,通過(guò)譯碼,你被翻譯成 4 個(gè)微指令。

譯碼的過(guò)程可以劃分為多步。譯碼過(guò)程中的一步是檢查你需要的數據和猜測你可能會(huì )產(chǎn)生一個(gè)地址跳轉。譯碼器一旦檢測到需要的額外數據,不需要讓你知道,這個(gè)數據就開(kāi)始從內存加載到數據緩存中了。

你的四個(gè)微指令到達寄存器重命名表。你告訴它你需要讀哪個(gè)內存地址(比如說(shuō) fs:[eax+18h]),然后寄存器重命名表將這個(gè)地址轉換為臨時(shí)地址供微指令使用。地址轉化完成后,你的微指令將進(jìn)入重排序緩存(Reorder Buffer, ROB)并記錄指令次序。接著(zhù)第一時(shí)間進(jìn)入保留站(Reservation Station, RS)。

保留站用于存儲已經(jīng)準備就緒可以執行的指令。你的第三條微指令被立即選中并送往端口5,這個(gè)端口直接執行運算。但是你并不知道為什么它會(huì )被首先選中,無(wú)論如何,它確實(shí)被執行了。幾個(gè)時(shí)鐘周期之后你的第一條微指令前往端口2,該端口是讀單元(Load Address地址 execution unit)。剩余的微指令一直等待,同時(shí)各個(gè)端口正在收集不同的微指令。他們都在等待端口 2 將數據從緩存和內存中加載進(jìn)來(lái)并放在臨時(shí)存儲空間內。

他們等了很久……

相當久的時(shí)間……

不過(guò)在他們等待第一條微指令返回數據的時(shí)候,又有其他的新指令又進(jìn)來(lái)。好在處理器知道如何讓這些指令亂序執行(即后到達保留站的微指令被優(yōu)先執行)。

當第一條微指令返回了數據,剩余的兩條微指令被立即送往執行端口 0 和1.現在這 4 條微指令都已經(jīng)運行,最終它們會(huì )返回保留站。

這些微指令返回后交出他們的“票”并給出各自的臨時(shí)地址。通過(guò)這些地址,你作為一個(gè)完整的指令,將他們合并。最后 CPU 將結果交給你并使你退出

當你到達標有“退出”的門(mén)的時(shí)候,你會(huì )發(fā)現這里要排一個(gè)隊列。你進(jìn)入后發(fā)現你剛好站在你前面進(jìn)來(lái)指令的后面,即使執行中的順序可能已經(jīng)不同,但你們退出的順序繼續保持一致??磥?lái)亂序執行部件真正知道自己做了什么。

每條指令最終離開(kāi) CPU,每次一條指令,就和指令指針指向的順序一樣!

結論

希望這篇小文能夠給讀者展示一些處理器工作的奧秘,要知道,這并不是魔術(shù)。

讓我們回到最初的問(wèn)題,現在我們應該可以給出一些較好的答案了。

處理器內部是如何工作的呢?在這個(gè)復雜的過(guò)程中,指令首先被分解為更小的微指令命令,這些微指令以亂序的方式盡可能快的被執行,然后按照原始的順序提交執行結果。因此,從外部看來(lái),所有的指令都是按照順序的方式執行的。但是現在我們知道,處理器內部是以亂序的方式處理指令的,有時(shí)甚至以猜測的方式來(lái)運行分支代碼。

運行一條指令究竟需要多長(cháng)時(shí)間呢?對于沒(méi)有使用流水線(xiàn)技術(shù)的處理器來(lái)說(shuō),這是一個(gè)容易回答的問(wèn)題,但對于現代的處理器來(lái)說(shuō),一條指令的執行時(shí)間與它周?chē)噶畹膬热菀约芭R近 cache 的大小和內容都有關(guān)。一條指令通過(guò)處理器有一個(gè)最小的時(shí)間,但只能粗略的說(shuō)這個(gè)時(shí)間是恒定的。一個(gè)好的程序員和編譯器可以讓很多條指令同時(shí)運行,從而使每條指令的分攤時(shí)間幾乎為零。這里說(shuō)的幾乎為零的執行時(shí)間并不是指一條指令的總的執行時(shí)間很短,相反,通過(guò)整個(gè)亂序部件和等待內存讀寫(xiě)數據是需要花費很多時(shí)間的。

一個(gè)新的處理器擁有 12 級或者 18 級、甚至更深的 31 級流水線(xiàn)意味著(zhù)什么呢?這意味著(zhù)更多的指令可以被同時(shí)送進(jìn)加工廠(chǎng)。一個(gè)非常深的流水線(xiàn)可以讓幾百條指令同時(shí)被處理。當一切順利時(shí),一個(gè)亂序部件可以保持高速運轉,從而獲得驚人的吞吐量。不幸的是,深的流水線(xiàn)同時(shí)意味著(zhù)流水線(xiàn)停頓會(huì )從一個(gè)相對可以容忍的性能損失變成一個(gè)可怕的性能噩夢(mèng)。因為幾百條指令都不得不停頓下來(lái),等待流水線(xiàn)恢復運轉。

我怎么根據這些信息來(lái)優(yōu)化程序呢?幸運的是,CPU 可以在大部分常見(jiàn)情況下工作良好,并且編譯器已經(jīng)為亂序處理器優(yōu)化了近 20 年。當指令和數據按照順序(沒(méi)有煩人的跳轉)執行時(shí),CPU 可以獲得最好的性能。因此,首先,使用簡(jiǎn)單的代碼。簡(jiǎn)單直接的代碼會(huì )幫助編譯器的優(yōu)化引擎識別并優(yōu)化代碼。盡量不使用跳轉指令,當你不得不跳轉時(shí),盡量每次跳轉到同樣的方向。復雜的設計,例如動(dòng)態(tài)跳轉表,雖然看起來(lái)很酷并且的確可以完成非常強大的功能,但不管是處理器還是編譯器,都無(wú)法進(jìn)行很好的預測處理,因此復雜的代碼很可能導致流水線(xiàn)停頓和猜測錯誤,從而極大的損害處理器性能。其次,使用簡(jiǎn)單的數據結構。保持數據順序、相鄰和連續可以阻止數據停頓。使用正確的數據結構和數據分布可以獲得很大的性能提升。只要保持代碼和數據結構盡量簡(jiǎn)單,剩下的工作就可以放心地交給編譯器的優(yōu)化引擎來(lái)完成了。


上一頁(yè) 1 2 下一頁(yè)

關(guān)鍵詞:

評論


相關(guān)推薦

技術(shù)專(zhuān)區

關(guān)閉
国产精品自在自线亚洲|国产精品无圣光一区二区|国产日产欧洲无码视频|久久久一本精品99久久K精品66|欧美人与动牲交片免费播放
<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>