int main()
{
00211000 push ebp
00211001 mov ebp,esp
00211003 sub esp,10h
char charVar1;
short shortVar1;
int intVar1;
long longVar1;
charVar1 = 11;
00211006 mov byte ptr [charVar1],0Bh
shortVar1 = 11;
0021100A mov eax,0Bh
0021100F mov word ptr [shortVar1],ax
intVar1 = 11;
00211013 mov dword ptr [intVar1],0Bh
longVar1 = 11;
0021101A mov dword ptr [longVar1],0Bh
}
其他数据类型不通过寄存器,但只有短类型通过寄存器。怎么了?
GCC也做同样的事情,使用mov reg, imm32
/mov m16, reg
而不是mov mem, imm16
。
这是为了避免16位操作数大小为mov imm16
的英特尔P6系列CPU上的LCP暂停
与没有前缀的相同机器代码字节相比,当前缀改变指令其余部分的长度时,就会发生LCP(长度改变前缀(暂停。
mov word ptr [ebp - 8], 11
将涉及66
前缀,该前缀使指令的其余部分为5字节(操作码+modrm+disp8+imm16(,而不是相同操作码/modrm的7字节(操作代码+modrm+disp8+imm32(。(
66 c7 45 f8 0b 00 mov WORD PTR [ebp-0x8],0xb
c7 45 f8 0b 00 00 00 mov DWORD PTR [ebp-0x8],0xb
^
opcode
这种长度变化混淆了在机器代码块被路由到实际解码器之前发生的指令长度查找阶段(预解码(。他们被迫备份并使用一种较慢的方法,该方法以查看操作码的方式考虑前缀。(x86机器代码的并行解码很难(。根据微体系结构和指令的对齐情况,此备份的代价可能高达11个周期,如果可能,应避免。
请参阅在简单的x86_64指令上,长度更改前缀(LCP(是否会导致暂停了解什么是长度变化前缀暂停的详细信息,以及在英特尔P6和SnB系列CPU中暂停预解码阶段几个周期的性能影响,以及Sandybridge系列(现代主流英特尔(特殊情况下的mov
操作码,以避免LCP从16位立即暂停。
mov
在现代英特尔上没有问题
Sandybridge家族专门为mov
删除了LCP暂停(对于其他指令仍然存在(,所以这个调优决定只对Nehalem和更早的版本有帮助。
AFAIK,这不是Silvermont家族的事情,也不是任何AMD的事情,所以这可能是MSVC和GCC应该为他们的tune=generic
更新的事情,因为P6家族CPU现在越来越不相关了。(如果GCC/MSVC的最新开发版本现在发生了变化,那么还需要一年左右的时间才能用新的编译器构建出许多软件发行版。(
clang
没有进行这种优化,即使在旧的P6系列CPU上也不是灾难,因为大多数软件都不使用大量的short
/int16_t
变量。(瓶颈并不总是前端,通常是缓存未命中。(
示例
将该函数存储到堆栈中当然是由于没有实现优化。由于这些变量不是volatile
,因此应该对它们进行完全优化,因为以后不会有任何东西读取它们。当你想做asm输出的例子时,不要写main
,写一个必须有一些副作用的函数,例如通过指针存储,或者使用volatile
。
void foo(short *p){
volatile short x = 123;
*p = 123;
}
用MSVC 19.14-O2
编译(https://godbolt.org/z/eWhzhEsEa):
x$ = 8
p$ = 8
foo PROC ; COMDAT
mov eax, 123 ; 0000007bH
mov WORD PTR x$[rsp], ax
mov WORD PTR [rcx], ax
ret 0
foo ENDP
或者使用GCC11.2-O3
,它甚至更糟,而不是CSE调用/重用寄存器常数
foo:
mov eax, 123
mov edx, 123
mov WORD PTR [rsp-2], ax
mov WORD PTR [rdi], dx
ret
但我们可以看到,这是英特尔的调整,因为-O3 -march=znver1
(AMD Zen 1(:
foo:
mov WORD PTR [rsp-2], 123
mov WORD PTR [rdi], 123
ret
不幸的是,它仍然用-march=skylake
来避免mov
的LCP,所以它不知道完整的规则。
如果我们使用*p += 12345;
(一个大到不能放在imm8
中的数字,与mov不同,它允许加法(而不是仅使用=
,具有讽刺意味的是,GCC会使用带有-march=skylake
的长度可变前缀(MSVC也是如此(,从而创建一个stall:add WORD PTR [rdi], 12345
。