Intel Core Microarchitecture Pipeline
Intel微處理器近20年從Pentium發展到Skylake,得益於制作工藝上的巨大發展,處理器的性能得到了非常大的增強,功能模塊增多,不過其指令處理pipeline的主幹部分算不上有特別大的變化,更多的是為了提高指令的處理速度添加一些模塊以及各模塊的增強與優化。
本文會以Intel Core微處理器架構為例去了解Intel微處理器pipeline的各個功能模塊。
Core架構概覽
上圖以指令的處理流程(pipeline)的方式對Core微處理器的架構進行了劃分,指令通過各個功能模塊最終實現指令的處理。需要註意的是,上圖只是描畫出pipeline的大致部分,本文也僅僅是對上圖中的這些模塊展開討論,另外的某些模塊或者細節會在以後的文章中講述。
指令處理的pipeline如果按照指令的in-order/out-of-order的方式進行劃分的話可以分成三部分:
- 指令的抓取以及解碼是in-order的。指令流按照原本的順序被抓取,然後送到解碼器進行解碼,這部分可以稱為前端(Front-End)。
- 指令的執行是out-of-order的。指令在解碼得到μops之後,會經過Renamer以及Reservation Station從而使μops支持out-of-order,然後經由Scheduler分發到各個Execution Unit執行。
- 指令的休止是in-order的。指令執行完成後,在Re-Order Buffer內對執行結果重新排序,使得執行結果順序輸出。
Front End
Instruction Fetch Unit
指令獲取單元包含以下部件:instruction translation lookaside buffer(ITLB),instruction prefetcher,instruction cache,predecode unit。
其中ITLB用於存放虛擬地址向物理地址的轉換表,目的是免去每次從內存查看頁表的操作;instruction prefecher用於抓取接下來可能會執行的指令,instruction cache用於存儲接下來要執行的指令;predecode unit用於指令的初步解碼。
Instruction Cache and ITLB
指令是以16字節對齊的方式取近CPU的,在讀取指令時,會通過ITLB得到正確的物理地址,從內存中把指令讀取到instruction cache以及instruction prefetch buffer。一旦將要執行的指令在cache中,則會把該16字節的指令發送到predecoder。一般來說,程序的指令平均長度為4字節,也就是說平均每一次可以抓取4條指令。
如果一段目標代碼的開頭不是16字節對齊的,則會導致fetch效率的下降
如果接下來的指令為跳轉分支,則可能會造成解碼效率的下降
在一般的代碼中,約每10條指令會出現一個分支,也就是說平均每3~4個時鐘周期就會出現一次上述的情況。
不過,通常前端不會成為指令處理的瓶頸,因為pipeline的其他某些部分的效率無法達到每個時鐘周期處理4條指令的程度,除非程序中有大量throughput非常高的指令,並且這些指令之間能很好地搭配出幾條依賴鏈(如采用SSE2的音視頻處理內核)。
Instruction PreDecode
指令預解碼單元會接收到從instruction cache或者instruction prefetch buffer發出的16字節,並執行以下任務:
- 解碼出指令的長度
- 解碼出指令的所有前綴
- 為指令打上標記(如“是分支 / is branch”)
預解碼單元每個時鐘周期可以向instruction queue寫入6條指令。預解碼的後面獲取的指令只能等待前面獲取的指令解碼完畢後才能開始解碼。例如,某一次獲取的16字節中含有7條指令,那麽預解碼單元會在第一個時鐘周期寫入6條指令,第二個時鐘周期寫入1條指令,而後續獲取的指令會在第三個時鐘周期進行解碼,如此一來,前兩個時鐘周期平均解碼的指令為3.5條。
另外,有兩個前綴會導致解碼變慢。這兩個前綴會動態地改變指令的長度,被稱為length changing prefixes(LCPs)。
- 66H Operand-Size Prefix 這個前綴會改變操作數的長度。比如在32位處理器中,默認操作數長度為32位,也就是說所用到的寄存器為EAX、EBX等(mov eax, 0xef)。如果使用了AX、BX等寄存器(mov ax, 0xef),此時操作數長度為16位,則會在指令前面添加前綴66H。
- 67H Address-Size Prefix 這個前綴會改變地址的位數。比如在32位處理器中,默認的地址為32位(mov eax, [edx]),如果某條指令希望用16位進行尋址的話(mov eax, [dx]),則會在指令前面添加前綴67H。
這兩個前綴在IA-32系列之前的處理器上會導致predecode的延遲,不過在新系列的處理器上只有特定情況才會有副作用,比如說工作在保護模式的處理器,具體情況請自行查看資料。
Instruction Queue
Instruction Queue位於predecode unit與decoder之間,他最多可以存儲18條指令,並且每個時鐘周期可以向decoder發送5條指令,5條指令中只能包含1條macro-fusion指令。
Instruction Queue(IQ)最大的作用就是偵測並存儲循環指令。IQ內提供了能存儲小於18條指令的loop cache,如果loop stream detector(LSD)偵測到IQ內的指令為循環內的指令,則會把這些指令鎖定下來,那麽在循環期間就可以直接從IQ中獲取指令到解碼器,從而省去前面fetch以及predecode等工作。如此一來能很好地提高效率以及降低功耗。
總結來說LSD可以提供以下便利:
- 不會因為循環的跳轉而影響效率。
- 不會因為循環的第一條指令不對齊而影響效率。
- 降低前端的功耗,因為在循環期間instruction cache,predecode unit等可以處於空閑狀態。
不過如果想很好地利用這個功能的話就需要把循環內指令的大小控制在一定範圍內。
※後續的處理器在decoder與execution unit之間添加了Instruction Decoder Queue,LSD也移動到了這個位置。
Instruction Decode
Core微處理器中包含了四個指令解碼器。其中第一個解碼器Decoder 0的功能最強,可以解碼最高為4 μops的指令,其余的三個解碼器只能解碼單μop的指令。
盡管解碼器存在上述分工,不過得益於某些改進策略(micro-fusion,macro-fusion,Stack Pointer Tracker),因此很多在execution unit時會被拆分成多個μops分別執行的指令在decoder階段都會被當成單一的μop,如此一來那三個只能解碼單μop的解碼器就可以執行更多的解碼任務,指令也沒必要按照4-1-1-1的方式進行配置了。
Micro-code sequencer ROM (MSROM)
MSROM用於存儲復雜指令的μops。對於那些大於4μops或者相當復雜(不常用)的指令,是無法通過decoder進行解碼的,此時decoder會請求MSROM的協助,MSROM收到請求後會傳輸與指令對應的μops到Decode Pipeline/Decoded ICache上。
使用MSROM指令會導致解碼時間出現較大的增加,因此應盡量避免使用MSROM指令。比如,對於PUSH與CALL指令來說,如果操作數為內存,則該指令的解碼需要尋求MSROM的協助,但是如果操作數為寄存器,則可以由decoder直接解碼。因此應當先把數據從內存mov進寄存器,再執行PUSH或CALL的操作。
Instruction from MSROM | Recommendation Replacement Instructions |
CALL m16/m32/m64 | Load + CALL reg |
PUSH m16/m32/m64 | Store + RSP update |
(I)MUL r/m16 (Result DX:AX) | Use (I)MUL r16, r/m16 extended precision not required, or (I)MUL r32, r/m32 |
(I)MUL r/m32 (Result EDX:EAX) | Use (I)MUL r32/m32 if extended precision not required, or (I)MUL r64, r/m64 |
(I)MUL r/m64 (Result RDX:RAX) | Use (I)MUL r64, r/m64 if extended precision not required |
其他如DIV,CPUID,AESDEC等指令也是MSROM指令,MSROM指令一般都會有較高的latency以及1/throughput,具體哪條指令為MSROM指令以及相關數據還請查看Intel 64 and IA-32 Architectures Optimization Reference Manual。
The Out-of-Order Engine
散序引擎(Out-of-Order Engine)能大大提高指令的執行效率。散序引擎通過偵測各指令間的依賴關系,使得指令形成各不相幹的依賴鏈,那麽某條依賴鏈上的指令就能在其它依賴鏈等待資源時進入execution unit執行,實現了對處理器的充分利用。正是這種基於資源的處理方式使得指令打破了其原本的處理順序,故稱為Out-of-Order。散序引擎包含三個主要部分。
Renamer/Allocator
指令在前端經過解碼後會得到μops隊列,Renamer/Allocator對隊列中的μop執行以下操作:
- 把μops相關的邏輯寄存器重命名為物理寄存器,用Register alias table(RAT)維護邏輯寄存器與物理寄存器之間的映射關系。
- 為μops開辟資源,如load或store所需的臨時buffer。
- 把μops與合適的端口進行綁定。
其中寄存器重命名與Out-of-Order尤為相關。如果發現μops對整個寄存器的寫入操作,Renamer會為邏輯寄存器映射到新的物理寄存器,如此一來就切斷了依賴鏈,為Out-of-Order execution打下了基礎。
Scheduler (Reservations station)
Scheduler(RS)的目的是把μops分配到相應的execution port。為了實現這個目的,RS必須具備識別μop是否ready的能力。當μop的所有的源(source)都就位時則表明該μop為ready,可以讓RS調度到execution unit執行。RS會根據Issue Port的可用情況以及已就緒μops的優先級來對μop進行調度。
RS會為每個進入其中的μop開辟一項,用於存儲正在等待資源的μop,Core微處理器中的RS可以容納32項μops。
在scheduler之前的μops的順序都是in-order的,經過scheduler之後μops變為了out-of-order。
Retirement (Reorder buffer)
Reorder buffer(ROB)主要用於記錄μops的狀態以及存儲EU執行完成後返回的結果,然後按照in-order的順序把執行結果寫回寄存器,使得在用戶層看來指令在以in-order的順序執行,in-order寫回的這一步被稱為retirement。Core微處理器一共可以容納96項μops。
Out-of-Order Engine在Sandy Bridge微處理器後進行了大幅度的改變,相關細節請查看Re-Order Buffer。
Execution Units
在RS的指令就緒後,RS會通過issue port(IP)把指令調度到相應的EU執行,Core微處理器一共有6個IP,也就是說一次性最多可以發出6個μops。在執行完成指令後,除了少數指令之外(如store),一般都需要把執行結果寫回寄存器(或者說ROB),經由writeback port(WP)進行寫回,Core微處理器一共有4個WP。
下面的表為Core微處理器(06-0FH)以及Enhanced Core微處理器(06-17H)的port及其目的端的EU的功能以及Latency、Throughout。
Executable Operation | Latency, Throughput | Comment1 | |
Signature=06_0FH | Signature=06_17H | ||
Interger ALU Interger SIMD ALU FP/SIMD/SSE2 Move and Logic |
1, 1 1, 1 1, 1 |
1, 1 1, 1 1, 1 |
Includes 64-bit mode integer MUL; Issue port 0; Writeback Port 0; |
Single-precision(SP) FP MUL Double-precision FP MUL |
4, 1 5, 1 |
4, 1 5, 1 |
Issue port 0;Writeback port 0 |
FP MUL (x87) FP Shuffle DIV/SQRT |
5, 2 1, 1 -, -8 |
5, 2 1, 1 -, -8 |
Issue port 0;Writeback port 0 FP shuffle does not handle QW shuffle |
Integer ALU Integer SIMD ALU FP/SIMD/SSE2 Move and Logic |
1, 1 1, 1 1, 1 |
1, 1 1, 1 1, 1 |
Excludes 64-bit mode integer MUL; Issue port 1;Writeback port 1; |
FP ADD QW Shuffle |
3, 1 1, 12 |
3, 1 1, 13 |
Issue port 1; Writeback port 1; |
Integer loads FP loads |
3, 1 4, 1 |
3, 1 4, 1 |
Issue port 2; Writeback port 2; |
Store address4 | 3, 1 | 3, 1 | Issue port 3; |
Store data5 | Issue port 4; | ||
Integer ALU Integer SIMD ALU FP/SIMD/SSE2 Move and Logic |
1, 1 1, 1 1, 1 |
1, 1 1, 1 1, 1 |
Issue port 5; Writeback port 5 |
QW shuffles 128-bit Shuffle/Pack/Unpack |
1, 12 2-4, 2-46 |
1, 13 1-3, 17 |
Issue port 5; Writeback port 5 |
NOTES:
- 如果向同一個port調度不同latency的操作,有可能會導致writeback bus沖突,因而影響性能。例如向port 1:clock 0發出的操作的latency為2,clock 1發出的操作的latency為1,那麽在clock 2時兩者都需要writeback port 1進行寫回,此時必然會導致其中的一個操作延遲寫回。
- 當Core執行128-bit shuffle操作時會有較長的latency並降低throughput。
- Enhanced Core獨立出來了128-bit shuffle操作,在port 5,並且提升了執行速度。
- 為store的寫回做準備,相關的地址操作。
- 為store的寫回做準備,相關的數據操作,數據寫回內存與cache、memory相關,其中涉及到一些處理邏輯,後面分析。
- Core中的128-bit Shuffle/Pack/Unpack等操作用的都是QW shuffle單元。
- Enhanced Core中128-bit Shuffle/Pack/Unpack等操作有獨立的128-bit單元。
- DIV/SQRT等操作的latency與throughput是數據相關的。通俗地說就是,簡單的DIV/SQRT的latency會較短,復雜的DIV/SQRT的latency則較長。
Advanced Memory Access
Intel處理器一般都含有多級data cache。Core微處理器就含有兩級data cache,其中每個核心內部的為L1 data cache、兩個核心共用外部的L2 cache。
一個核心內部包含的數據訪問相關部分有:
- L1 data cache,也被稱為data cache unit(DCU),它可以同時處理多個cache misses以及stores/loads;能維護cache coherency(cache一致性)。cache line為64-byte。
- Data translation lookaside buffer(DTLB),即緩存在核心內的虛擬物理地址轉換表。Core的實現分為分兩層:DTLB0、DTLB1。DTBL0用於loads,DTLB1用於stores以及作為DTBL0的loads misses候補。
- Page miss handler(PMH),當所請求虛擬地址轉換的頁表不在TLB時,會由PMH進行處理。
- Memory ordering buffer(MOB),由load buffer與store buffer組成,用於支持loads與stores的out of order處理、預測(speculative)處理。
Load Buffer and Store Buffer
Core微處理器的load unit以及store unit一個時鐘周期可以從DCU中讀取或者寫入最多128-bit。DCU以及EU之間有load buffer與store buffer,用於暫存load與store指令訪問的數據。
- load指令在Allocator期間,會被分配一個load buffer,在load EU執行期間把數據從DCU讀入load buffer,然後通過writeback port把數據寫回ROB,在retirement時會把數據從ROB寫入register。
- store指令在Allocator期間,會被分配一個store buffer,在store EU執行期間把目標內存地址以及數據寫入store buffer,在retirement後會把數據寫入DCU。
內存數據的存儲相比其它普通的指令來說,運行時間受到更多因素的影響,如cache miss、總線忙、數據不對齊等,而加入load buffer以及store buffer能有效降低讀寫內存時受到的負面影響,把數據留在離EU更近的地方,使得對內存數據的處理顯得更為本地化,即對EU來說,所要處理的數據都更容易獲取,割離前後端(讀寫內存)的影響(in-flight)。
Out of Order
微處理器中相關的內存讀寫操作是out of order的,不過out of order的內存操作需要遵循以下規則:
- 對於老的微處理器來說(如P6),在一個store的地址沒搞清楚之前,它後面的loads都不能提到該store之前,等到地址確定後才能做出抉擇。不過新系列的處理器可以通過預測來決定load是否可以提前,請參考下面的Memory Disambiguation。
- 如果訪問的是同一個地址,load不能提到位於它前面的store之前。
- 對於老的微處理器來說(如P6),stores之間的順序不能調換,比較新的微處理器似乎可以?
如下圖所示:
Speculate
由於指令的執行分為fetch、decode、execution、retirement等多個階段,不同的微處理器細分開來可以有十多到二十多級的pipeline。執行到分支(Branch)的時候,按理來說是需要等條件的最終執行結果出來才能知道接下來走那個分支,不過如此一來,在等待執行結果的途中pipeline中的各部分器件會處於空閑狀態,導致資源浪費以及指令執行效率不高。
解決方法就是微處理器會預測接下來會走哪個分支,微處理器把該分支的指令獲取並處理。不過由於該分支只是預測分支,實際並不一定會走該分支,因此分支內的指令不應該執行retirement步驟,直到確定了會走該分支。如果實際上不走預測分支,則需要把預測分支清空,重新執行另一分支。
Load的retirement是把數據從ROB寫入register,而Store的retirement是把數據從store buffer寫入DCU。預測分支內的Load與Store就不會執行這些步驟,直到確定會走該預測分支。
Memory Disambiguation
前面在討論Out of Order的內存訪問時,說到在老的微處理器中,如果指令Store的地址未知時,位於其後的Load指令必須等到計算出該Store的地址後才能判斷Load是否能提前,不過新的處理器采用了Memory Disambiguation(MD)技術,使得可以在不知道Store地址的情況下決定是否提前執行Load。
我們先來看看Load提前會得到怎麽樣的好處
在采用Memory Disambiguation前,上面的指令需要9個時鐘周期才能完成,而采用了該技術之後僅需5個時鐘周期即可完成,Load提前執行使得位於其後的指令也能提前執行,進而充分利用了各個不同功能的EU。據統計,在一般的工作站中,Load占全部指令的10~25%,Store占比30~45%,可以想象采用MD技術後,指令的執行效率會有相當的提升。
那麽在MD中,Load提前到一個未知地址的Store之前執行的以依據是什麽?微處理器主要根據過去執行指令來進行預測,準確率達到90%以上。不過由於是預測,因此不一定準確,也就如上述speculate的討論一樣不能執行retirement步驟,直到算出Store的地址。
Store Forwarding
如果在一個store後緊跟著對同一地址的load,那麽load就能直接從store中獲取所需的數據,而不用再次去到內存中獲取,這種技術被稱為Store Forwarding(ST)。ST需要滿足以下幾個條件:
- store必須為相同地址的load之前的最後一個store。
- store所存儲的數據必須等於或大於load所讀取的數據。
- load的數據不能超越cache line邊界(Core之後的微處理器去除了這個限制)。
- load的數據不能超越8字節的邊界(Core之後的微處理器去除了這個限制),除非是16字節的讀取。
- load的地址必須跟store的地址一樣(Nehalem之後的微處理器去除了這個限制),除非是以下的情況:
- 64 bit的store可以forward它任意一半32 bit。
- 128 bit的store可以foward它任意一半的64 bit。
- 128 bit的store可以foward它任意四分之一的32 bit。
總體來說,隨著微處理器的更新,限制越來越少,不過具體還請查看相關資料。
Data Prefetch to L1 caches
Core微處理器提供了兩個預取數據的硬件(hardware prefetcher),用於預先獲取數據進L1 data cache (DCU):
- Data cache unit (DCU) prefetcher - 也就是常說的流預取器(stream prefetcher),一旦發現當前獲取的某個數據的地址是不久前獲取的某個數據地址的遞增,就會觸發該prefetcher,處理器會認為我們在訪問一段連續的數據,因此會自動把其遞增地址上的下一個cache line獲取進入cache。
- Instruction pointer (IP)-based strided prefetcher - 該prefetcher會跟蹤load指令,如果發現load指令的地址之間存在相同的間隔,那麽該prefetcher就會被觸發,去預取下一個間隔所在的地址的數據。這種預取可以是前向也可以是後向的,並且間隔的大小最大可以到達2KB。
數據的預取還需要滿足以下幾個條件:
- load的數據是從writeback類型的內存(內存訪問的一種cache類型,writeback簡單地說就是load與store都要經過cache)中獲取的。
- 要預取的數據在一頁(page boundary of 4KB)之內。
- pipeline在處理過程中不會進行限制與鎖定。
- 在load的過程中沒有太多的cache miss。
- 總線不是太忙。
- 沒有連續的流寫回操作(continuous stream of stores)。
Data Prefetch to L2 caches
L2 cache上的數據預取由Data prefetch logic(DPL)進行管理。L2要從內存預獲取什麽數據,依據的是DCU過去從L2訪問過什麽數據。DPL維護了兩個獨立的隊列,用於存儲DCU訪問的數據的地址:一條隊列是用於存儲upstreams,即遞增的地址,有12項;另一條隊列用於存儲downstreams,即遞減的地址,有4項。每一項用於跟蹤一頁(4KB)的地址訪問情況。
DPL會監控DCU對於連續遞增序列(incremental sequences,known as streams)的讀取情況。一旦DPL偵測到DCU在訪問的數據是流,則會預讀取下一條cache line。如DCU向L2要求訪問cache line A以及A+1,那麽DPL會認為DCU接下來需要訪問A+2,那麽就會去預取cache line A+2,如果DCU訪問了A+2,那麽DPL就會預取A+3。遞減序列同理。
上面描述的是DPL的基礎功能。DPL在Pentium M微處理器首次引入,在Core微處理器時已經有相當強大的功能,如:能判斷何時跳過某些不需要的cache lines;每次處理兩個預讀;預取的cache line可以比load的數據領先8個cache line;可以根據總線的繁忙程度動態調整等。
Reference:
Intel 64 and IA-32 Architectures Software Developer‘s Manual
Intel® 64 and IA-32 Architectures Optimization Reference Manual
Agner Fog - The microarchitecture of Intel, AMD and VIA CPUs
Operand size prefix in 16-bit mode
David Kanter - The Memory Aliasing Problem
David Kanter - Memory Disambiguation, the Solution
Intel Core Microarchitecture Pipeline