64-ia-32架構優化手冊(15)
3.4.2. 取指與解碼優化
Intel Core微架構提供了幾個機制來增加前端吞吐量。利用這些特性的技術在下面討論。
3.4.2.1. 對微融合的優化
工作在一個暫存器及一個記憶體運算元上的一條指令解碼得到的微操作比對應暫存器-暫存器版本要多。使用暫存器-暫存器版本替換前者指令等效的工作通常要求一個2條指令的序列。這個序列很可能導致取指頻寬的降低。
Assembly/Compiler程式設計規則18.(影響ML,普遍性M)要提高取指/解碼吞吐率,一條指令的記憶體形式(flavor)要優先於其僅使用暫存器的形式,如果這樣的指令可以從微融合獲益。
下面例子是可以由所有的解碼器處理的某些微融合型別:
- 所有對記憶體的寫,包括寫立即數。在內部寫執行兩個獨立的微操作:寫地址(store-address)與寫資料(store-data)。
- 所有在暫存器與記憶體間進行“讀-修改(read-modify,load+op)” 的指令,例如:
ADDPS XMM9, OWORD PTR[RSP+40]
FADD DOUBLE PTR [RDI+RSI*8]
XOR RAX, QWORD PTR [RBP+32]
- 所有形式為“讀且跳轉”的指令,例如:
JMP [RDI+200]
RET
- 帶有立即運算元與記憶體運算元的CMP與TEST。
一條帶有RIP相對取址的Intel 64指令,在以下情形中不會微融合:
- 在需要一個額外的立即數時,例如:
CMP [RIP+400], 27
MOV [RIP+3000], 142
- 當需要一個RIP用於控制流目的時,例如:
JMP [RIP+5000000]
在這些情形裡,Intel Core微架構以及Intel微架構Sandy Bridge從解碼器0提供一個2個微操作的流,導致瞭解碼頻寬是輕微損失,因為2個微操作的流必須從與之匹配解碼器前進到解碼器0。
在訪問全域性資料時,RIP取址是常見的。因為它不能從微融合獲益,編譯器可能考慮以其他記憶體取址方式訪問全域性資料。
3.4.2.2. 對巨集融合的優化
巨集融合將兩條指令合併為一個微操作。Intel Core微架構在有限的環境下這些這個硬體優化。
巨集融合對的第一條指令必須是一條CMP或TEST指令。這條指令可以是REG-REG,REG-IMM,或者一個微融合的REG-MEM比較。第二條指令(在指令流中相鄰)應該是一個條件分支。
因為這些對在基本的迭代式程式設計序列中是常見的,即使在非重新編譯的二進位制程式碼中巨集融合也能提高效能。所有的解碼器每週期可以解碼一個巨集融合對,連同最多3條其他指令,形成每週期5條指令的解碼頻寬峰值。
每條巨集融合後指令使用單個分發執行。這個過程降低了時延,因為從分支誤預測的損失中移除了一個週期。軟體還可以獲得其他的融合好處:增加了重新命名與回收頻寬,用於正在進行指令的更多儲存,在更少位元中表示更多工作帶來的能耗降低。
下面的列表給出你何時可以使用巨集融合的細節:
- 在進行比較時,CMP或TEST可以被融合:
REG-REG。例如:CMP EAX,ECX; JZ label
REG-IMM。例如:CMP EAX,0x80; JZ label
REG-MEM。例如:CMP EAX,[ECX]; JZ label
MEM-REG。例如:CMP [EAX],ECX; JZ label
- 使用所有條件跳轉的TEST可以被融合。
- 在Intel Core微架構中,僅使用以下條件跳轉的CMP可以被融合。這些條件跳轉檢查進位標記(CF)或零標記(ZF)。能夠進行巨集融合的條件跳轉是:
JA或JNBE
JAE或JNB或JNC
JE或JZ
JNA或JBE
JNAE或JC or JB
JNE或JNZ
在比較MEM-IMM時(比如CMP [EAX], 0X80; JZ label),CMP與TEST不能被融合。Intel Core微架構在64位模式中不支援巨集融合。
- Intel微架構Nehalem在巨集融合中支援以下增強:
- 帶有以下條件跳轉的CMP可以被融合(在Intel Core微架構中不支援):
-
- JL或JNGE
- JGE或JNL
- JLE或JNG
- JG或JNLE
-
- 在64位模式在支援巨集融合。
- 在Intel微架構Sandy Bridge中增強的巨集融合支援總結在表3-1中,在2.3.2.1節與例子3-15中有額外的資訊。
表3-1. 在Intel微架構Sandy Bridge中可巨集融合指令
指令 |
TEST |
AND |
CMP |
ADD |
SUB |
INC |
DEC |
JO/JNO |
Y |
Y |
N |
N |
N |
N |
N |
JC/JB/JAE/JNB |
Y |
Y |
Y |
Y |
Y |
N |
N |
JE/JZ/JNE/JNZ |
Y |
Y |
Y |
Y |
Y |
Y |
Y |
JNA/JBE/JA/JNEB |
Y |
Y |
Y |
Y |
Y |
N |
N |
JS/JNS/JPE/JNP/JPO |
Y |
Y |
N |
N |
N |
N |
N |
JL/JNGE/JGE/JNL/JLE/JNG/JG/JNLE |
Y |
Y |
Y |
Y |
Y |
Y |
Y |
Assembly/Compiler程式設計規則19.(影響M,普遍性ML)儘可能應用巨集融合,使用支援巨集融合的指令對。如果可能優先使用TEST。在可能時使用無符號變數以及無符號跳轉。嘗試邏輯化地驗證一個變數在比較時刻是非負的。在可能時避免MEM-IMM形式的CMP或TEST。不過,不要新增任何指令來避免使用MEM-IMM形式的指令。
例子3-11. 巨集融合,無符號迭代計數
沒有巨集融合 |
有巨集融合 |
|
C程式碼 |
for (int[1] i = 0; i < 1000; i++) a++; |
for (unsigned int[2] i = 0; i < 1000; i++) a++; |
彙編 |
for (int i = 0; i < 1000; i++) mov dword ptr[i], 0 jmp First Loop: mov eax, dword ptr [i] add eax, 1 mov dword ptr [i], eax First: cmp dword ptr [i], 3E8H[3] jge End a++; mov eax, dword ptr [a] addqq eax, 1 mov dword ptr [a], eax jmp Loop End: |
for (unsigned int i = 0; i < 1000; i++) xor eax, eax mov dword ptr [i], eax jmp First Loop: mov eax, dword ptr [i] add eax, 1 mov dword ptr [i], eax First: cmp eax, 3E8H[4] jae End a++; mov eax, dword ptr [a] add eax, 1 mov dword ptr [a], eax jmo Loop End: |
例子3-12. 巨集融合,if語句
沒有巨集融合 |
有巨集融合 |
|
C程式碼 |
int[5] a = 7; if (a < 77) a++; else a--; |
unsigned int[6] a = 7; if (a < 77) a++; else a--; |
彙編 |
int a = 7; mov dword ptr [a], 7 if (a < 77) cmp dword ptr [a], 4DH[7] jge Dec a++; mov eax, dword ptr [a] add eax, 1 mov dword ptr [a], eax else jmp End a--; Dec: mov eax, dword ptr [a] sub eax, 1 mov dword ptr [a], eax End:: |
unsigned int a = 7; mov dword ptr [a], 7 if (a < 77) mov eax, dword ptr [a] cmp eax, 4DH jae Dec a++; add eax, 1 mov dword ptr [a], eax else jmp End a--; Dec: sub eax, 1 mov dword ptr [a], eax End:: |
Assembly/Compiler程式設計規則20.(影響M,普遍性ML)當可以在邏輯上確定一個變數在比較時刻是非負的,軟體可以啟用巨集融合;在比較一個變數與0時,適當地使用TEST來啟用巨集融合。
例子3-13. 巨集融合,有符號變數
沒有巨集融合 |
有巨集融合 |
test ecx, ecx jle OutSideTheIF cmp ecx, 64H jge OutSideTheIF <IF BLOCK CODE> OutSideTheIF: |
test ecx, ecx jle OutSideTheIF cmp ecx, 64H jae OutSideTheIF <IF BLOCK CODE> OutSideTheIF: |
對於有符號或無符號變數“a”;就標記而言,“CMP a, 0”與“TEST a, a”產生相同的結果。因為TEST更容易巨集融合,為了啟用巨集融合,軟體可以使用“TEST a, a”替換“CMP a, 0”。
例子3-14. 巨集融合,有符號比較
C程式碼 |
沒有巨集融合 |
有巨集融合 |
if (a == 0) |
cmp a, 0 jne lbl ... lbl: |
test a, a jne lbl ... lbl: |
if ( a >= 0) |
cmp a, 0 jl lbl; ... lbl: |
test a, a jl lbl ... lbl: |
Intel微架構Sandy Bridge使更多使用條件分支的算術與邏輯指令能巨集融合。在迴圈中在ALU埠已經擁塞的地方,執行其中一個巨集融合可以緩解壓力,因為巨集融合指令僅消耗埠5,而不是一個ALU埠加上埠5。
在例子3-15中,迴圈“add/cmp/jnz”包含了兩個可以通過埠0,1,5分發的ALU指令。因此埠5繫結其中一條ALU指令的可能性更高,導致JNZ等一個週期。迴圈“sub/jnz”,ADD/SUB/JNZ可以在同一個週期裡分發的可能性增加了,因為僅SUB可繫結到埠0,1,5。
例子3-15. 在Intel微架構Sandy Bridge中額外的巨集融合好處
Add + cmp + jnz的替代方案 |
帶有sub + jnz的迴圈控制 |
lea rdx, buff xor rcx, rcx xor eax, eax loop: add eax, [rdx + 4 * rcx] add rcx, 1 cmp rcx, LEN jnz loop |
lea rdx, buff - 4 xor rcx, LEN xor eax, eax loop: add eax, [rdx + 4 * rcx] sub rcx, 1 jnz loop |
3.4.2.3. 長度改變字首
一條指令的長度最多可以是15個位元組。某些字首可以動態地改變解碼器所知道的一條指令的長度。通常,預解碼單元將假定沒有LCP,估計指令流中一條指令的長度。當預解碼器在取指行中遇到一個LCP時,它必須使用一個更慢的長度解碼演算法。使用這個更慢的解碼演算法,預解碼器在6個週期裡解碼取指行,而不是通常的1週期。機器流水線正常排隊吞吐率通常不能隱藏LCP帶來的效能損失。
可以動態改變一條指令長度的字首包括:
- 運算元大小字首(0x66)。
- 地址大小字首(0x67).
在基於Intel Core微架構的處理器以及在Intel Core Duo與Intel Core Solo處理器中,指令MOV DX, 01234h受制於LCP暫停。包含imm16作為固定編碼部分,但不需要LCP來改變這個立即數大小的指令不受LCP暫停的影響。在64位模式中,REX字首(4xh)可以改變兩類指令的大小,但不會導致一個LCP效能損失。
如果在一個緊湊的迴圈中發生LCP暫停,它會導致顯著的效能下降。當解碼不受一個瓶頸時,就像在有大量浮點的程式碼中,孤立的LCP暫停通常不會造成效能下降。
Assembly/Compiler程式設計規則21.(影響MH,普遍性MH)傾向生成使用imm8或imm32值,而不是imm16值的程式碼。如果需要imm16,將相同的imm32讀入一個暫存器並使用暫存器中字的值。
兩次LCP暫停
受制於LCP暫停且跨越一條16位元組取指行邊界的指令會導致LCP暫停被觸發兩次。以下對齊情況會觸發兩次LCP暫停:
- 使用一個MODR/M與SIB位元組編碼的一條指令,且取指行邊界在MODR/M與SIB位元組之間。
- 使用暫存器與立即數字節偏移取址模式訪問記憶體,從取指行偏移13處開始的一條指令。
第一次暫停是對第一個取指行,第二次暫停是對第二個取指行。兩次LCP暫停導致一個11週期的解碼效能損失。
下面的例子導致LCP暫停一次,不管指令第一個位元組在取指行的位置:
ADD word ptr [EDX], 01234H
ADD word ptr 012345678H[EDX], 01234H
ADD word ptr [012345678H], 01234H
以下指令在取指行偏移13處開始時,導致兩次LCP暫停:
ADD word ptr [EDX+ESI], 01234H
ADD word ptr 012H[EDX], 01234H
ADD word ptr 012345678H[EDX+ESI], 01234H
為了避免兩次LCP暫停,不要使用受制於LCP暫停,使用SIB位元組編碼或位元組位移取址(byte displacement)模式的指令。
偽LCP暫停(False LCP Stall)
偽LCP暫停具有與LCP暫停相同的特徵,但發生在沒有任何imm16值的指令上。
當(a)帶有LCP、使用F7操作碼編碼的指令,(b)位於取指行的偏移14處時,發生偽LCP暫停。這些指令是:not,neg,div,idiv,mul與imul。偽LCP造成時延,因為在下一個取指行之前指令長度解碼器不能確定指令的長度,這個取指行持有在指令MODR/M位元組中的實際操作碼。
以下技術可以輔助避免偽LCP暫停:
- 將F7指令組的所有短操作提升為長操作,使用完整的32不位元版本。
- 確保F7操作碼不出現在取指行的偏移14處。
Assembly/Compiler程式設計規則22.(影響M,普遍性ML)確保使用0xF7操作碼位元組的指令不在取指行的偏移4處開始;避免使用這些指令操作16位資料,將短資料提升為32位。
例子3-16. 避免伴隨0xF7指令組的偽LCP暫停
在解碼器中導致時延的一個序列 |
避免時延的替代序列 |
neg word ptr a |
movsx eax, word ptr a neg eax mov word ptr a, AX |
[1] 有符號的迭代計數阻止巨集融合。
[2] 無符號的迭代計數與巨集融合相容。
[3] CMP MEM-IMM, JGE阻止巨集融合。
[4] CMP REG-IMM, JAE允許巨集融合。
[5] 有符號迭代計數阻止巨集融合。
[6] 無符號迭代計數與巨集融合相容。
[7] CMP MEM-IMM, JGE阻止巨集融合。