我是x86-64的初学者,尤其是在性能优化方面,我正在尝试变得更好。
我已经阅读了agner's optimization manual volume 2的各个部分。反复指出,进入16字节对齐的关键热点/循环非常重要。现在,我很难弄清楚循环中的条目是否对齐16字节。
您是否应该将循环入口之前子例程中每条指令的字节开销加起来,看看它是否可以除以16?我已咨询了x86-64的英特尔开发人员手册,但无法读取其中哪些指令具有哪些字节长度的信息。指令的字节大小是否只是将操作码加在一起?因此,对于带有操作码REX.W + 8C的MOV r64 / m16,大小为2个字节吗? (一个用于REX.W前缀,一个用于8C)。
考虑以下代码,假设某些字符串作为参数传递给rdi,该参数将在.LmanipulationLoop中进行操作:
string_fun:
cmp cl, byte ptr [rdi]
jz .Lend
xor rcx, rcx
.LmanipulationLoop
*some string operation*
.Lend
ret
所以基于我目前的理解:
总共(假设我是对的)5个字节。现在这是否意味着我需要在.LmanipulationLoop之前需要11个NOP才能确保对齐进入循环?
[您不需要需要手动执行此操作,汇编程序可以为您执行此操作。仅当您希望比仅使用NOP填充以在对齐后立即对齐时更聪明时,手动计算才有用。插入填充的位置。
通常,在标签前使用.p2align 4
(GAS)或.p2align 4
(NASM 1)可使汇编器确定需要多少填充,并发出一个或多个长的NOP。 (不是11个单字节NOP,那会很糟糕,因为它们每个人都必须分别解码)。
和/或使用调试器或反汇编器检查标签地址,而不是手动计算,如果您的目标是[C0
如果要尽量减少所需的NOP数量,了解有关哪些指令的长度是很有用的,但是在这种情况下,可以通过反复试验来找到一个好的指令序列,从而使您无需最长时间的NOP。
通常真正重要的是uop缓存行的32字节边界。对于具有循环缓冲区的CPU上的大多数小循环来说,还是根本没有(但是请注意,通过微码更新禁用了Skylake / Kaby Lake的LSD以修复错误)。如果避免从uop缓存中获取前端瓶颈,则非常关键的循环的顶部的32字节对齐可能会很有用。或者对于每个循环可以运行1个周期的微小循环,将整个循环放在同一uop缓存行中是必不可少的(否则前端每次迭代需要两个循环来获取它)。
不幸的是,Skylake派生CPU上的循环对齐的主要问题是对齐循环的bottom以解决性能隐患 align 16
。
我修复了您源代码中的错误(标签后缺少What methods can be used to efficiently extend instruction length on modern x86?,以及使用32位操作数大小对RCX进行零运算的性能错误)。尽管在这种情况下,您可能想要a jcc
or macro-fused compare+branch that touches a 32-byte boundary disables the uop cache for that line只是为了使其更长一些,因为您知道需要一些NOP字节。 jcc
会更好,而不是:
。
并且我用SIMD加载填充了占位符。
xor rcx,rcx
与REX.W=0
或hurt performance on Silvermont组装。.intel_syntax noprefix
.p2align 4 # align the top of the function
string_fun:
cmp cl, byte ptr [rdi]
jz .Lend
xor ecx, ecx # zeroing ECX implicitly zero-extends into RCX, saving a REX prefix
lea rsi, [rdi + 1024] # end pointer
# .p2align 4 # emit padding until a 2^4 boundary
.LmanipulationLoop: # do {
movdqu xmm0, [rdi]
# Do something like pcmpeqb / pmovmskb with the string bytes ...
add rdi, 16
cmp rdi, rsi
jb .LmanipulationLoop # }while(p < endp);
.Lend:
ret
使gcc -Wa,--keep-locals -c foo.S
标签在目标文件的符号表中可见。
然后用as --keep-locals foo.s
拆解:
--keep-locals
.L
的注释,汇编器将发出一个3字节的NOP:objdump -drwC -Mintel foo.o
反汇编0000000000000000 <string_fun>:
0: 3a 0f cmp cl,BYTE PTR [rdi]
2: 74 16 je 1a <.Lend>
4: 31 c9 xor ecx,ecx
6: 48 8d b7 00 04 00 00 lea rsi,[rdi+0x400]
# note address of this label,
# or without --keep-locals, of the instruction that you know is the loop top
000000000000000d <.LmanipulationLoop>:
d: f3 0f 6f 07 movdqu xmm0,XMMWORD PTR [rdi]
11: 48 83 c7 10 add rdi,0x10
15: 48 39 f7 cmp rdi,rsi
18: 72 f3 jb d <.LmanipulationLoop> # note the jump target address
000000000000001a <.Lend>:
1a: c3 ret
目标文件不会显示调用外部函数的合理地址;它尚未链接,因此尚未填充rel32位移。但是.p2align 4
将显示重定位信息。并在汇编时完全解决了源文件中的跳转问题。
脚注1:请注意,NASM的默认设置很差,您需要类似的方法来获取长的NOP,而不是多个单字节NOP:
0000000000000000 <string_fun>:
0: 3a 0f cmp cl,BYTE PTR [rdi]
2: 74 19 je 1d <.Lend>
4: 31 c9 xor ecx,ecx
6: 48 8d b7 00 04 00 00 lea rsi,[rdi+0x400]
d: 0f 1f 00 nop DWORD PTR [rax] # This is new, note that it's *before* the jump target
0000000000000010 <.LmanipulationLoop>:
10: f3 0f 6f 07 movdqu xmm0,XMMWORD PTR [rdi]
14: 48 83 c7 10 add rdi,0x10
18: 48 39 f7 cmp rdi,rsi
1b: 72 f3 jb 10 <.LmanipulationLoop>
000000000000001d <.Lend>:
1d: c3 ret