bal*_*c55 109 optimization performance x86 assembly micro-optimization
以下所有说明都做同样的事情:设置%eax
为零.哪种方式最佳(需要最少的机器周期)?
xorl %eax, %eax
mov $0, %eax
andl $0, %eax
Run Code Online (Sandbox Code Playgroud)
Pet*_*des 200
TL; DR摘要:xor same, same
是所有CPU的最佳选择.没有其他方法比它有任何优势,它至少比任何其他方法都有一些优势.它是由英特尔和AMD正式推荐的.在64位模式下,仍然使用xor r32, r32
,因为写32位寄存器会将上面的32复位. xor r64, r64
是浪费一个字节,因为它需要一个REX前缀.
更糟糕的是,Silvermont只承认xor r32,r32
破坏而不是64位操作数.因此,即使因为你将r8..r15归零而仍然需要REX前缀,请使用xor r10d,r10d
,而不是xor r10,r10
.
例子:
xor eax, eax ; RAX = 0. Including AL=0 etc.
xor r10d, r10d ; R10 = 0
xor edx, edx ; RDX = 0
; small code-size alternative: cdq ; zero RDX if EAX is already zero
; SUB-OPTIMAL
xor rax,rax ; waste of a REX prefix, and extra slow on Silvermont
xor r10,r10 ; bad on Silvermont (not dep breaking), same as r10d everywhere else because a REX prefix is still needed for r10d or r10.
mov eax, 0 ; doesn't touch FLAGS, but not faster and takes more bytes
and eax, 0 ; false dependency. (Microbenchmark experiments might want this)
sub eax, eax ; same as xor on most but not all CPUs; bad on Silvermont for example.
xor al, al ; false dep on some CPUs, not a zeroing idiom. Use xor eax,eax
mov al, 0 ; only 2 bytes, and probably better than xor al,al *if* you need to leave the rest of EAX/RAX unmodified
Run Code Online (Sandbox Code Playgroud)
归零矢量寄存器通常最好用pxor xmm, xmm
.这通常是gcc所做的(甚至在使用FP指令之前).
xorps xmm, xmm
可以有意义.它比一个字节短一个字节pxor
,但xorps
在Intel Nehalem上需要执行端口5,同时pxor
可以在任何端口(0/1/5)上运行.(Nehalem在整数和FP之间的2c旁路延迟延迟通常是不相关的,因为无序执行通常可以在新的依赖链的开始处隐藏它).
在SnB系列微体系结构中,xor-zeroing的味道都不需要执行端口.在AMD和预Nehalem的P6/2英特尔,xorps
和pxor
被处理的相同方式(如向量整数指令).
使用AVX版本的128b向量指令也会将reg的上半部分vpxor xmm, xmm, xmm
归零,因此对于归零YMM(AVX1/AVX2)或ZMM(AVX512)或任何将来的向量扩展是一个很好的选择. vpxor ymm, ymm, ymm
但是,不需要任何额外的字节来编码,并且运行相同.AVX512 ZMM归零将需要额外的字节(对于EVEX前缀),因此应首选XMM或YMM归零.
有些CPU认为k0..7
是类似的归零vpcmpeqd
,但所有识别任何归零习惯用语的CPU都能识别vpternlogd
.只需使用sub same,same
,您就不必担心哪个CPU识别哪个归零成语.
xor
(作为公认的归零成语,不像xor
)有一些明显的和一些微妙的优点(摘要列表,然后我将扩展那些):
xor
.(所有CPU)较小的机器代码大小(2个字节而不是5个)始终是一个优势:更高的代码密度导致更少的指令缓存未命中,更好的指令获取和潜在的解码带宽.
在Intel SnB系列微体系结构上不使用 xor 执行单元的好处很小,但节省了功耗.它更可能与SnB或IvB有关,它只有3个ALU执行端口.Haswell以及后来有4个执行端口可以处理整数ALU指令,包括xor
,所以通过调度程序完美的决策(实际上不会发生),HSW仍然可以维持每个时钟4个uop,即使它们都需要执行端口.
有关更多详细信息,请参阅我关于归零寄存器的另一个问题的答案.
Bruce Dawson的博客帖子 Michael Petch链接(在对问题的评论中)指出mov reg, 0
在注册重命名阶段处理而不需要执行单元(在未融合域中为零uops),但错过了它仍然是一个uop的事实在融合域中.现代英特尔CPU可以每个时钟发出和退出4个融合域uop.这就是每时钟限制4个零的来源.寄存器重命名硬件的复杂性增加只是将设计宽度限制为4的原因之一.(Bruce撰写了一些非常优秀的博客文章,比如他关于FP数学和x87/SSE /舍入问题的系列文章,我这样做极力推荐).
在AMD Bulldozer系列CPU上,mov reg,0
运行在相同的EX0/EX1整数执行端口上mov r32, imm32
. xor
也可以在AGU0/1上运行,但这仅用于寄存器复制,而不是用于设置.所以,据我所知,在AMD公司唯一的优势mov immediate
过分xor
的是较短的编码.它也可能节省物理寄存器资源,但我还没有看到任何测试.
公认的归零成语避免了对Intel CPU的部分寄存器处罚,后者将部分寄存器与完整寄存器(P6和SnB系列)分开重命名.
mov reg,reg
将标记寄存器为具有上部归零,所以xor
/ mov
/ xor
避免了通常的局部寄存器惩罚该IVB预CPU具有.即使没有xor eax, eax
,当inc al
修改高8位()然后读取整个寄存器时,IvB只需要合并uop ,而Haswell甚至会删除它.
来自Agner Fog的微型指南,第98页(Pentium M部分,后面的部分包括SnB参考):
处理器将自身的XOR识别为将其设置为零.寄存器中的特殊标记会记住寄存器的高位为零,因此EAX = AL.即使在循环中也会记住此标记:
Run Code Online (Sandbox Code Playgroud)# Good: xorps xmm0, xmm0 ; smallest code size (for non-AVX) pxor xmm0, xmm0 ; costs an extra byte, runs on any port on Nehalem. xorps xmm15, xmm15 ; Needs a REX prefix but that's unavoidable if you need to use high registers without AVX. Code-size is the only penalty. # Good with AVX: vpxor xmm0, xmm0, xmm0 ; zeros X/Y/ZMM0 vpxor xmm15, xmm0, xmm0 ; zeros X/Y/ZMM15, still only 2-byte VEX prefix #sub-optimal AVX vpxor xmm15, xmm15, xmm15 ; 3-byte VEX prefix because of high source reg vpxor ymm0, ymm0, ymm0 ; decodes to 2 uops on AMD before Zen2 # Good with AVX512 vpxor xmm15, xmm0, xmm0 ; zero ZMM15 using an AVX1-encoded instruction (2-byte VEX prefix). vpxord xmm30, xmm30, xmm30 ; EVEX is unavoidable when zeroing zmm16..31, but still prefer XMM or YMM for fewer uops on probable future AMD. May be worth using only high regs to avoid needing vzeroupper in short functions. # Good with AVX512 *without* AVX512VL (e.g. KNL / Xeon Phi) vpxord zmm30, zmm30, zmm30 ; Without AVX512VL you have to use a 512-bit instruction. # sub-optimal with AVX512 (even without AVX512VL) vpxord zmm0, zmm0, zmm0 ; EVEX prefix (4 bytes), and a 512-bit uop. Use AVX1 vpxor xmm0, xmm0, xmm0 even on KNL to save code size.
(来自第82页):只要您没有得到中断,错误预测或其他序列化事件,处理器就会记住EAX的高24位为零.
该引导件的pg82还证实,inc eax
被未识别为归零成语,至少在早期的设计P6像PIII或PM.如果他们花费晶体管在后来的CPU上检测它,我会感到非常惊讶.
xor
设置标志,这意味着在测试条件时必须小心.由于AH
遗憾的是只能使用8位目的地,因此您通常需要注意避免部分注册处罚.
如果x86-64将一个被移除的操作码(如AAM)重新用于16/32/64位mov reg, 0
,并且在r/m字段的源寄存器3位字段中编码谓词,那将是很好的.一些其他单操作数指令将它们用作操作码位).但他们没有这样做,无论如何这对x86-32没有帮助.
理想情况下,您应该使用xor
/ set flags setcc
//读取完整寄存器:
; Example 7.9. Partial register problem avoided in loop
xor eax, eax
mov ecx, 100
LL:
mov al, [esi]
mov [edi], eax ; No extra uop
inc esi
add edi, 4
dec ecx
jnz LL
Run Code Online (Sandbox Code Playgroud)
这在所有CPU上都具有最佳性能(无停顿,合并uop或错误依赖).
当你不想在标志设置指令之前进行xor时,事情会变得更复杂.例如,你想在一个条件上分支,然后在同一个标志的另一个条件下setcc.例如setcc r/m
,xor
您要么没有备用寄存器,要么完全不使用setcc
未采用的代码路径.
没有公认的归零成语不会影响标志,因此最佳选择取决于目标微体系结构.在Core2上,插入合并uop可能会导致2或3个周期停顿.它似乎在SnB上更便宜,但我并没有花太多时间来测量.使用cmp/jle
/ sete
会对较旧的英特尔CPU造成重大损失,并且在较新的英特尔上仍然会有所改善.
如果你不能在标志设置指令之前进行xor-zero,那么使用xor
/ mov reg, 0
可能是Intel P6和SnB系列的最佳选择.这应该比在xor-zeroing之后重复测试更好.(甚至不考虑setcc
/ setcc
或movzx r32, r8
/ sahf
).IvB可以消除lahf
(即使用寄存器重命名处理它,没有执行单元或延迟,如xor-zeroing).Haswell后来只消除了常规pushf
指令,所以popf
需要一个执行单元并且具有非零延迟,使得test/movzx r32, r8
/ mov
比movzx
/ test/差setcc
,但仍然至少和test/movzx
/ 一样好xor
(并且在旧CPU上要好得多).
在AMD/P4/Silvermont上使用setcc
/ mov r,0
没有归零是不好的,因为它们不会分别跟踪子寄存器的deps.寄存器的旧值会有一个错误的缺陷.当/ test/不是一个选项时,使用setcc
/ setcc
进行归零/依赖性破坏可能是最好的选择.movzx
mov reg, 0
当然,如果您不需要setcc
输出宽于8位,则不需要将任何内容归零.但是,如果选择最近属于长依赖关系链的寄存器,请注意除P6/SnB之外的CPU的错误依赖性.(如果你调用一个可以保存/恢复你正在使用的寄存器的函数,请注意引起部分注册失效或额外的uop.)
xor
具有立即零并不是特殊的,与我所知的任何CPU上的旧值无关,因此它不会破坏依赖链.它没有优点setcc
,也有许多缺点.
请参阅http://agner.org/optimize/获取microarch文档,包括哪些归零成语被识别为依赖性破坏(例如setcc
,在某些但不是所有CPU上,而在所有CPU and
上都被识别.) xor
确实打破了旧值的依赖关系链寄存器(无论源值如何,零或不,因为这是sub same,same
有效的). xor same,same
只有在src和dest是同一个寄存器的特殊情况下才会断开依赖链,这就是为什么它被mov
排除在特别识别的依赖断层列表之外.(另外,因为它不被认为是归零成语,具有其他好处.)
有趣的是,最古老的P6设计(PPro到Pentium III)并没有认识到mov
- 为了避免部分寄存器停顿而仅仅作为归零的惯用语,因此在某些情况下值得同时使用它们.(参见Agner Fog的例子6.17.在他的microarch pdf中.他说这也适用于P2,P3,甚至(早期?)PM. 对链接博客文章的评论说,只有PPro有这种疏忽,但我'我们在Katmai PIII上进行了测试,并且@Fanael在Pentium M上进行了测试,我们都发现它没有打破延迟限制xor
链的依赖性.)
如果它确实使您的代码更好或保存指令,那么确保零,mov
以避免触摸标志,只要您不引入除代码大小之外的性能问题.但是,避免使用破坏标志是不使用的唯一合理理由xor
.
归档时间: |
|
查看次数: |
36061 次 |
最近记录: |