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阻止宏融合。