以 Intel? sandy bridge 微架構(gòu)為例,了解一下 Intel 近代微架構(gòu)。
上圖是 Intel? sandy bridge 微架構(gòu)的流水線示意圖,實行了“發(fā)射-執(zhí)行-完成”相分離包括下面的組件(亂序執(zhí)行按序完成):
- in-order front-end:程序執(zhí)行順序的前端組件,包括了:
- L1 ICache 與 ITLB ,指令通過 ITLB 查找 fetch(提?。┻M入到 32K 的 L1 指令 cache。
- pre-decoder,一個預解碼器,主要用來解析指令的長度,處理 LCPs(length changing prefixes)。
- instruction queue,經(jīng)過初步解析后存放的指令隊列。
- decoder,4個解碼器。其中一個為復雜解碼器,能解碼所有 x86/x64 指令。三個簡單解碼器,負責解碼為一個 micro-op。
- decoded ICache,解碼后的 uops(micro-ops)cache。
- MSROM(microcode sequencer ROM),一個 microcode sequencer ROM(微代碼裝置 ROM),存放復雜指令的 micro-op 流。
- BPU(branch prediction unit),分支預測單元。
- micro-op queue,排列 decoded ICache 的 uops。
- out-of-order engine,亂序發(fā)射的引擎組件,包括了:
- allocater/renamer,資源分配器與重命名器。Renamer 將 x86 架構(gòu)性(architectural)的源/目標操作數(shù)(寄存器)重命令為微架構(gòu)性(microarchitectural)源/目標操作數(shù)(寄存器),解決 uops 間的 false-dependencies(假依賴),并形成 out-of-order 的“data flow”(數(shù)據(jù)流)發(fā)送到 scheduler。
Allocater 分配 uops 需要的 load buffer 以及 store buffer。
- scheduler,調(diào)度器。等待資源可用(dispatch port 可用,read buffer 或者 store buffer 可用)以及 uop 的操作數(shù)已經(jīng)準備好后,將 uop 綁定到相應 dispatch port 后分派到執(zhí)行單元。scheduler 每個 cycle 最多可以分派 6 個 uops 到執(zhí)行 port。
- in-order retirement,按序完成單元。使用 reorder buffer 保存 uops 各個階段的結(jié)果,確保 uops 的執(zhí)行結(jié)果(包括任何可能遇到的異常,中斷)按原始程序的次序完成。
- execution unit,執(zhí)行單元,含有 6 個執(zhí)行 port 以及 3 個類型的 stack。因此,schedular 每個 cycle 最多可以調(diào)度并分派 6 個 uops 到執(zhí)行 port。
- cache hierarchy,包括下面:
- L1 DCache:
- DCU(data cache unit)
- load buffers
- store buffers
- line fill buffers
- L1 ICache
- L2 cache
- LLC(last level cahce)
1. in-order front-end
在按序前端里,包括了下面的組件:
- L1 ICache 與 ITLB ,通過 ITLB 查找 fetch(提?。┻M入到 32K 的 L1 ICache。
- legacy decode pipeline,下面的組件被劃分到 legacy decode pipeline 里:
- pre-decoder,一個預解碼器,主要用來解析指令的長度,處理 LCPs(length changing prefixes)。
- instruction queue,經(jīng)過初步解析后存放的指令隊列。
- decoder,4個解碼器。其中一個為復雜解碼器,能解碼所有 x86/x64 指令。三個簡單解碼器,負責解碼為一個 micro-op。
- decoded ICache,解碼后的 uops(micro-ops)cache。
- MSROM(microcode sequencer ROM),一個 microcode sequencer ROM(微代碼裝置 ROM),存放復雜指令的 micro-op 流。
- BPU(branch prediction unit),分支預測單元。
- micro-op queue,排列 decoded ICache 的 uops。
1.1 ICache 與 ITLB
在指令提?。╥nstruction fetch)階段,處理器通過 ITLB 查找并從內(nèi)存的 16-byte 邊界上提取指令到 ICache 里。當 ICache hit 時引發(fā) ICache 每個 cycle 傳送 16 bytes 到指令 pre-decoder 組件里。
如果以平均每條指令 4 個字節(jié)來算,那么 ICache 能滿足每個周期 4 個 decoder 的解碼工作??梢哉J為,如果遇到較長指令的話(例如 10 個字節(jié)),ICache 傳送的 16 bytes 是不能滿足 decoder 的。
在 sandy bridge 微架構(gòu)上,ICache 與 ITLB 的明細信息:
- size: 32-Kbyte
- ways: 8
- ITLB 4K-page entries:128
- ITLB large-page(2M/1G)entries:8
也就是說,ICache 共有 32K,8-ways 結(jié)構(gòu)。ITLB 中維護 4K 頁面映射結(jié)果的表項有 128 個,維護 2M 與 1G 頁面映射結(jié)果的表項共有 8 個。
當產(chǎn)生 ITLB miss 時,處理器將在 STLB(second TLB,或者 shared TLB)里繼續(xù)查找。
ITLB miss 而在 STLB hit 時,所需要 7 個 cycles。當然,如果 STLB 也產(chǎn)生 miss 則需要在頁表結(jié)構(gòu)里進行 walk,將需要更多的 cycles。
1.2 pre-decoder
pre-decoder 接收從 ICache 發(fā)送過來 16 bytes 的指令,它主要執(zhí)行下面的工作:
- 確定指令的具體長度。
- 處理指令所有的 prefix。
- 標記指令的類型屬性。(例如:屬于分支指令)
pre-decoder 在每個 cycle 里,最多可以寫入 6 條指令到 instruction queue 里。也就是說:在每個 cycle 里,pre-decoder 最多可以從 16-bytes 里解析出 6 條指令放入 instruction queue。要達到每 cycle 6 條指令,這說明平均每條指令不能超過 2 個字節(jié)。
如果這 16 字節(jié)里包含多于 6 條指令(例如每條指令為 2 個字節(jié)),則在下一個 cycle 里 pre-decoder 會繼續(xù)按每 cycle 最多解碼 6 條指令進行解碼。
例如,fetch line(16 字節(jié))里含有 7 條指令,那么首 6 條指令會在一個 cycle 完成解碼寫入 instruciton queue 里,第 7 條指令將在下一個 cycle 里解碼。在下一個 cycle 里,ICache 會繼續(xù)發(fā)送 16 bytes 到 pre-pecoder 里,pre-pecoder 繼續(xù)最多解碼 6 條指令。
指令的 operand size 以及 address size 會影響著指令長度。我們知道 CS.D 決定了指令的 default operation size(默認的操作寬度)。當 CS.D = 1 時,默認的 operand size 與 address size 為 32 位。CS.D = 0 時,默認的 operand size 與 address size 為 16 位。
但是,default operand-size override prefix 與 default address-size override prefix 可以改變操作數(shù)與地址的寬度,從而改變了指令固定的長度,這兩個 prefix 被稱為
LCP(length changing prefix)。
- default operand-size override prefix(66H):重新改寫默認的 32 位或者 16 位 operand size 為 16 位或者 32 位。
例如:mov eax, 11223344h。它的指令編碼為 B8 44 33 22 11(指令長度為 5),當插入 66H 字節(jié)時,指令編碼為 66 B8 44 33(指令長度為 4)。
- default address-size override prefix(67H):重新改寫默認的 32 位或者 16 位 address size 為 16 位或者 32 位。
例如:mov eax, [11223344h],它的指令編碼為 A1 44 33 22 11(指令長度為 5),當插入 67H 字節(jié)時,指令編碼為 67 A1 44 33(指令長度為 4)
當然,也可能存在超過 1 個 LCP 的情況(同時存在 66H 與 67H)。因此,如果指令含有 LCP 的話,pre-decoder 需要確定最終的指令長度,在解碼 LCP 時需要額外花費 3 個 cycles (注:在前一代架構(gòu)中,解碼 LCP 需要花費 6 個 cycles)。
另外,REX prefix 雖然也能改變指令的長度(MOV reg, [disp32] 或者 MOV reg64, imme64),但 pre-decoder 解碼時并不會花費額外的 cycles。
1.3 instruction queue
instruction queue 組件在 pre-decoder 與 decoder 之間,經(jīng)過 pre-decoder 后指令的長度已經(jīng)確認,從 ICache 傳送過來的 16-bytes 被解析為 x86 指令寫入 instruction queue(指令隊列)里存放著,instruction queue 最多可以容納 18 條指令。由于 macro-fused(宏融合,兩條指令解碼為 1 個 uop)的存在,instruction queue 每個 cycle 最多傳送 5 條指令(其中包括了兩條可以宏融合的指令)到 decoder 進行解碼。
1.4 decoder
decoder 負責將 x86/x64 的 CISC 指令解碼為單一功能的 micro-ops(uops,微操作),從 core 微架構(gòu)開始就擁有 4 個 decoder(解碼器)。第 1 個 decoder(decoder 0) 是復雜解碼器,能解碼所有的 x86/x64 指令,
每個 cycle 最多可以解碼為 4 個 uops。其余 3 個為簡單解碼器,將簡單指令解碼為 1 個 uop,每個 cycle 只能解碼 1 個 uop。
第 1 個復雜解碼器也能解碼簡單指令,因此 4 個 decoder 都能解碼為 1 個 uop,包括 micro-fused(微融合),stack pointer tracking(棧指針跟蹤)以及 macro-fused(宏融合)。但是,只有一個 decoder 能解碼為 4 個 uops。那么,4 個 decoder 每個 cycles 最多可以解碼為 7 個 uops(4 + 1 + 1 + 1)。decoder 解碼后的 uops 被送入到 decoded ICache 以及 micro-op queue。
MSROM 組件負責提供復雜的 uops 數(shù)據(jù)流,在 sandy bridge 微架構(gòu)里 MSROM 每個 cycle 能提供 4 個 uops,MSROM 用來幫助 decoder 解碼超過 4 個 uops 的指令。因此,當指令超過 4 個 uops 將從 MSROM 里取得。向 MSROM 取 uops 的操作可以由 decoder 或者 decoded ICache 組件發(fā)起。
通過 macro-fusion(宏融合)技術(shù),decoder 能將兩條 x86 指令解碼為 1 個 uop。4 個 decoder 都可以產(chǎn)生 macro-fused 動作,但在每個 cycle 里 4 個 decoder 只能產(chǎn)生一個 macro-fused。因此,在每個 cycle 里 decoder 最多能解碼 5 條 x86 指令(2 + 1 + 1 + 1),最多能產(chǎn)生 7 個 uops。
1.4.1 micro-fusion(微融合)
x86/x64 指令允許使用“memory-to-register”類操作數(shù),當指令的操作數(shù)是 memory 與 register 時將解碼為多個 uops。例如:“ADD RAX, [RBX]”指令是一條典型的操作數(shù)為 memory 與 register 的指令,它會被解碼為兩個 uops:一個為 load uop,另一個為 add uop。
考查下面的一條 store 操作指令:
mov [rbx + rcx * 8 + 0Ch], rax
--------------------- ---
| |
| +--------> store data 操作(使用 port 4)
|
+------------------------> store address 操作(使用 port 2 或 port 3)
decoder 0 生成兩個 uops:一個是 store address 操作 uop,可以通過 port 2 或者 3 執(zhí)行。一個是 store data 操作 uop,通過 port 4 執(zhí)行。但在 dispatch 到 execution unit(執(zhí)行單元)時這兩個單一的 uop 被融合為一個復雜的 uop。
micro-fused(微融合)允許將多個 uops 融合為 1 個復雜的 uop 進行 dispatch 到 execution unit(執(zhí)行單元)。在發(fā)射到執(zhí)行單元時,這個復雜的 uop 與單一的 uop 花費同樣的 cycles。因此,使用 micro-fused 將提高從 decoder 發(fā)射到 execution unit 的吞吐量。但是,在執(zhí)行單元中仍然是執(zhí)行兩個 uops 操作。
從 core 微架構(gòu)開始引入了 micro-fusion 功能,可以將下面幾類操作進行 micro-fused:
- store 操作:包括了 sotre 寄存器與立即數(shù)。例如:“mov [rdi], rax”指令與“mov DWORD [rdi], 1”
- load-and-operation 操作:指令的操作數(shù)是 register 與 memory,執(zhí)行的是“l(fā)oad + op”操作(被解碼為 load uop 與另一個操作 uop)。
例如:“add rax, [rsi]”,“addps mmx0,[rsi]”,“xor rax, [rsi]”指令等等...
- load-and-jump 操作:這是一條分支指令,目標地址從 memory 地址 load 而來。例如:“jmp QWORD [rax]”,“call QWORD [rax]”指令。RET 指令也是屬于 micro-fusion 指令,因為它從 RSP 指向的棧里 load 目標地址(即返回地址)。
- memory 與 immediate 之間的 cmp-or-test 操作:CMP 或者 TEST 指令的操作數(shù)是 memory 與 immediate。例如:“cmp DWORD [rsi], 1”,“test DWORD [rsi], 1”指令等。
在 64-bit 模式下,指令使用 RIP-relative 尋址的 memory 時,在下面的情形下不能產(chǎn)生 micro-fused:
- 指令的另一個操作數(shù)是 immediate。例如:“mov DWORD [rip + 50h], 400”,“cmp QWORD [rip + 50h], 1”指令等。
- RIP-relative 尋址出現(xiàn)在分支指令里。例如:“ jmp QWORD [rip + 50h]”指令。
1.4.2 macro-fusion(宏融合)
macro-fused 將兩條 x86 指令融合為一個 uop,允許進行宏融合的兩條指令需要滿足下面條件:
- 第一條指令修改了 eflags/rflags 寄存器(不同微架構(gòu)所支持的指令也不同)。
- core, nehalem 微架構(gòu)上只支持 CMP 與 TEST 指令。
- sandy bridge 微架構(gòu)上支持 CMP, TEST, ADD, SUB, AND, INC 以及 DEC 指令。
如果存在兩個操作數(shù)(operand 1 與 operand 2),這些指令能產(chǎn)生 macro-fused 還需要滿足的條件是:operand 1(目標操作數(shù))是 register,并且 operand 2(源操作數(shù))是 immediate,register,或者非 RIP-relative 尋址的 memory。或者 operand 1 是 memory,而 operand 2 是 register。
- REG-REG:例如 cmp eax, ecx 指令。
- REG-IMM:例如 cmp eax, 1 指令。
- REG-MEM:例如 cmp eax, [esi] 指令(非 RIP-relative 尋址)。
- MEM-REG:例如 cmp [esi], eax 指令。
但是,MEM-IMM 操作數(shù)不能產(chǎn)生 macro-fused,例如 cmp DWORD [eax], 1 指令。
- 后面的指令是條件分支指令(Jcc 指令)。但是,不同的指令,以及不同的微架構(gòu)所支持的條件不同。
- TEST 指令所有的條件(所有的 eflags 標志位),包括:OF,CF, ZF, SF 以及 PF 標志位。
- CMP 指令根據(jù)不同微架構(gòu)支持不同的條件。
- core 微架構(gòu)僅支持 CF 與 ZF 標志位。因此,支持下面的 Jcc 指令:
- JC/JB/JNAE:CF = 1
- JNC/JNB/JAE:CF = 0
- JZ/JE:ZF = 1
- JNZ/JNE:ZF = 0
- JBE/JNA:CF = 1 or ZF = 1
- JA/JNBE:CF = 0 and ZF = 0
- nehalem 微架構(gòu)增加了對 SF <> OF 與 SF == OF 條件的支持:
- JL/JNGE:SF <> OF
- JNL/JGE:SF = OF
- JLE/JNG:SF <> OF or ZF = 1
- JNLE/JG:SF = OF and ZF = 0
- sandy bridge 微架構(gòu)增加了對 ADD, SUB, AND, INC 以及 DEC 指令的支持,它支持的條件如下表所示。
條件
|
分支指令
|
TEST
|
AND
|
CMP
|
AND
|
SUB
|
INC
|
DEC
|
OF = 1
|
JO
|
Y
|
Y
|
N
|
N
|
N
|
N
|
N
|
OF = 0
|
JNO
|
CF = 1
|
JC/JB/JNAE
|
Y
|
Y
|
Y
|
Y
|
Y
|
N
|
N
|
CF = 0
|
JNC/JNB/JAE
|
ZF = 1
|
JZ/JE
|
Y
|
Y
|
Y
|
Y
|
Y
|
Y
|
Y
|
ZF = 0
|
JNZ/JNE
|
CF = 1 or ZF = 1
|
JBE/JNA
|
Y
|
Y
|
Y
|
Y
|
Y
|
N
|
N
|
CF = 0 and ZF = 0
|
JNBE/JA
|
SF = 1
|
JS
|
Y
|
Y
|
N
|
N
|
N
|
N
|
N
|
SF = 0
|
JNS
|
PF = 1
|
JP/JPE
|
PF = 0
|
JNP/JPO
|
SF <> OF
|
JL/JNGE
|
Y
|
Y
|
Y
|
Y
|
Y
|
Y
|
Y
|
SF = OF
|
JGE/JNL
|
SF <> OF or ZF = 1
|
JLE/JNG
|
SF = OF and ZF = 0
|
JG/JNLE
|
上表中,Y 表示支持宏融合,N 表示不支持宏融合。
在 core 微架構(gòu)里,不支持 signed 數(shù)的比較產(chǎn)生宏融合(即 JL/JNGE, JGE/JNL, JLE/JNG 以及 JG/JNLE)。這個情況在 nehalem 微架構(gòu)里得到改善,支持 signed 數(shù)的比較結(jié)果產(chǎn)生宏融合。
1.4.3 stack pointer tracker
PUSH, POP, CALL, LEAVE 以及 RET 指令會隱式地更新 stack pointer 值,在 core 微架構(gòu)之后 decoder 負責維護這個隱式的更新 stack pointer 操作。
思考一下這條指令 “push rax”,它在以前的微架構(gòu)中會產(chǎn)生多個 uops,大概處理如下面所示:
(1) TEMP = RAX ;; ===> renaming ?
(2) RSP = RSP - 8 ;; ===> 生成 ALU uop
(3) [RSP] = TEMP ;; ===> 生成 STA(store address)uop 與 STD(store data)uop
那么,根據(jù)上面的拆分,decoder 大致可以解碼為 3 個 uops:1 個 SUB uop,1 個 STA(store address) uop 以及 1 個 STD(store data) uop。
再來看看這兩條指令“pop rax”與“ret”,大概處理如下面所示:
pop rax :
(1) rax = [RSP] ;; ===> 生成 LD uop
(2) RSP = RSP + 8 ;; ===> 生成 ADD uop
ret :
(1) RIP = [RSP] ;; ===> 生成 JMP uop
(2) RSP = RSP + 8 ;; ===> 生成 ADD uop
pop rax 指令可以解碼為 2 個 uop:1 個 LD(load data) uop 與 1 個 ADD uop。ret 指令可以解碼為 2 個 uop:1 個 JMP uop 與 1 個 ADD uop。
引進 stack pointer tracker (棧指針跟蹤器)這個功能后,將隱式棧指針更新操作移到 decoder 里實現(xiàn),從而釋放了 execution unit(執(zhí)行單元)資源,增加了發(fā)射與執(zhí)行帶寬。PUSH 指令需要 2 個 uops,而 POP 與 RET 只需要 1 個 uop。
1.5 decoded ICache
由于 x86 指令的不定長以及指令解碼 uops 數(shù)量的不同,需要使用 decoded ICache 來緩存從 decoder 里解碼出來的 uops。送入 decoder 進行解碼的 16 bytes 可能會解析出少于 4 條 x86 指條或者多于 4 條(按平均每條指令 4 個 bytes 來算),解碼出來的 uops 數(shù)量也會不同,而 out-of-order 執(zhí)行單元每個 cycle 最多允許執(zhí)行 6 個 uops。
造成前端的解碼與后端執(zhí)行的 uops 不匹配,引入 decoded ICache 能很大程度地緩解這些不匹配而帶來的 bandwidth(帶寬)瓶頸。
decoded ICache 是 8-ways 32-sets 結(jié)構(gòu),如下圖所示:
每 set 的每個 way 最多能容納 6 個 uops。因此,理想狀態(tài)下整個 decoded ICache 能緩存 6 * 8 * 32 = 1536 個 uops。
每個 way 裝載的 uops 是由 x86 指令字節(jié)里的 32 bytes 邊界解碼出來的(x86 指令 32 字節(jié)邊界對齊),也就是以 32 bytes 為一個塊,作為裝載單位。
- 如果 32 bytes 指令塊解碼出來不足 6 個 uops 時,則 way 不會被填滿而留下空位。下一個 32 bytes 指令塊解碼的 uop 會裝入下一個 way 里。
- 如果 32 bytes 指令塊解碼出來的 uops 超過 6 個時,表明一個 way 不能裝下全部 uops,則余下的 uops 會裝載到下一個 way 里。最多有 3 個連續(xù)的 ways 來容納這些 uops。也就是 32 bytes 的指令塊解碼出來的 uops 最多只能裝入 3 個 ways 里。那么,允許一個 32 bytes 指令塊最多只有 18(6 * 3) 個 uops 可以裝入 decoded ICache 中。
除了上面,way 的裝填還有一些限制:
- 如果一條 x86 指令解碼為多個 uops 時,這些 uops 不能跨 way 裝載,只能放入同一個 way 里。
- 每個 way 里最多只能裝入 2 個分支 uops
- 當指令從 MSROM 里獲得 uops 時,這些 uops 必須獨占一個 way。
- 非條件分支 uop 必須是 way 里的最后一個 uop。
- 如果指令含有 64 位的立即數(shù),這個立即數(shù)必須占用兩個 way。
當由于這些限制而造成 uops 不能裝入 decoded ICache 時(例如 32 bytes 指令塊解碼出來可能超過 18 個 uops),這些 uops 被直接發(fā)送到 out-of-order engine。
decoded ICache 是 L1-ICache(instruction cache)和 ITLB 對應的一份 shadow cache,ICache 存放的是 x86 指令字節(jié)碼,而 decoded ICache 存放的是 uops。也就是說:decoded ICache 里緩存的任何一個 uops 都在 ICache 里存在著對應的 x86 指令。那么,刷新 ICache lines 的指令時,也必須刷新 decoded ICache 里指令對應 uops。
當 ITLB 的某個 entry(或全部)被刷新時,可能造成整個 ICache 被刷新,同時也會使得整個 decoded ICache 被刷新。例如:更新 CR3 寄存器值從而更新了整個頁轉(zhuǎn)換表結(jié)構(gòu)(或者 CR4 寄存器某些頁機制相關(guān)的控制位被更新)。
1.6 BPU (branch prediction unit)
流水線前端利用 BPU(分支預測單元)盡可能地在確定分支指令的執(zhí)行路徑之前就預測出分支的目標地址。
BPU 能預測下面的分支類型:
- conditional branches(條件分支):也就是 Jcc 指令族。
test eax, eax
jz @taken
... ... ;; 分支跳轉(zhuǎn)不成立
@taken:
... ... ;; 分支跳轉(zhuǎn)成立
BPU 預測這個分支跳轉(zhuǎn)的目標地址。也就是預測這個跳轉(zhuǎn)是否成立。
- direct calls/jumps(直接的調(diào)用與跳轉(zhuǎn)),它們的目標地址是基于 RIP 與 offset 值而來。如下面代碼所示:
jmp @target ;; 直接跳轉(zhuǎn)
... ...
@target:
... ...
call @fun ;; 直接調(diào)用
- indirect calls/jumps(間接的調(diào)用與跳轉(zhuǎn)),它們的目標地址從 register 或 memory 里讀取。如下面代碼所示:
@fun: __func
... ...
@target: __target
jmp DWORD [@target] ;; 間接跳轉(zhuǎn)
;;
;; 或者:
;; mov eax, __target
;; jmp eax
... ...
__target:
... ...
call DWORD [@fun] ;; 間接跳轉(zhuǎn)
;;
;; 或者:
;; mov eax, __func
;; call eax
- returns(調(diào)用返回),也就是 RET 或 RET n 指令。使用 16 個 entries 的 RSB(return stack buffer)結(jié)構(gòu)實現(xiàn)。
|