whi*_*ear 5 c x86 assembly cpu-architecture micro-optimization
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
}
Run Code Online (Sandbox Code Playgroud)
其他数据类型不通过寄存器,但只有短类型通过寄存器。怎么了?
Pet*_*des 12
GCC 做了同样的事情,使用mov reg, imm32
/mov m16, reg
代替mov mem, imm16
.
这是为了避免 16 位操作数大小的 Intel P6 系列 CPU 上出现 LCP 停顿mov imm16
。
与没有前缀的相同机器代码字节相比,当前缀改变了指令其余部分的长度时,就会发生 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
Run Code Online (Sandbox Code Playgroud)
这种长度变化混淆了指令长度查找阶段(预解码),该阶段发生在机器代码块被路由到实际解码器之前。他们被迫备份并使用一种较慢的方法,在他们查看操作码的方式中考虑前缀。(x86 机器代码的并行解码很困难)。根据微架构和指令的对齐方式,此备份的损失可能高达 11 个周期,因此应尽可能避免。
请参阅长度更改前缀 (LCP) 是否会导致简单 x86_64 指令停顿?有关什么是长度更改前缀停顿的详细信息,以及将 Intel P6 和 SnB 系列 CPU 中的预解码阶段停顿几个周期的性能影响,以及 Sandybridge 系列(现代主流 Intel)特殊情况mov
避免 16 位立即数造成 LCP 停顿的操作码。
mov
特别是在现代英特尔上没有问题Sandybridge 系列mov
专门删除了 LCP 停顿(对于其他指令仍然存在),因此此调整决策仅对 Nehalem 和更早的版本有帮助。
AFAIK,这不是 Silvermont 系列的问题,也不是任何 AMD 的问题,所以这可能是 MSVC 和 GCC 应该更新的内容,tune=generic
因为现在 P6 系列 CPU 的相关性越来越低。(如果 GCC / MSVC 的最新开发版本现在发生变化,则需要大约一年左右的时间才能使用新编译器构建许多软件发行版/版本。)
clang
不进行此优化,即使在旧的 P6 系列 CPU 上这也不是一场灾难,因为大多数软件不使用大量short
/int16_t
变量。(瓶颈并不总是前端,通常是缓存未命中。)
该函数完全存储到堆栈当然是由于未启用优化。由于这些变量不是volatile
,因此应该将它们完全优化掉,因为以后没有任何东西会读取它们。当您想要制作asm输出的示例时,不要编写a main
,而是编写必须有一些副作用的函数,例如通过指针存储,或者使用volatile
.
void foo(short *p){
volatile short x = 123;
*p = 123;
}
Run Code Online (Sandbox Code Playgroud)
使用 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
Run Code Online (Sandbox Code Playgroud)
或者使用 GCC11.2 -O3
,这更糟糕,而不是CSEing /重用寄存器常量
foo:
mov eax, 123
mov edx, 123
mov WORD PTR [rsp-2], ax
mov WORD PTR [rdi], dx
ret
Run Code Online (Sandbox Code Playgroud)
但我们可以看到这是英特尔自-O3 -march=znver1
(AMD Zen 1)以来的调整:
foo:
mov WORD PTR [rsp-2], 123
mov WORD PTR [rdi], 123
ret
Run Code Online (Sandbox Code Playgroud)
mov
不幸的是,它仍然对with进行 LCP 避免-march=skylake
,因此它不知道完整的规则。
如果我们使用*p += 12345;
(一个足够大的数字,无法放入imm8
,与 mov 不同,add 允许)而不是仅仅使用=
,具有讽刺意味的是,GCC 然后使用长度变化前缀-march=skylake
(如 MSVC 一样),创建一个停顿: add WORD PTR [rdi], 12345
。
归档时间: |
|
查看次数: |
158 次 |
最近记录: |