以下所有指令都执行相同的操作:将%eax设置为零。哪种方式是最佳的(需要最少的机器周期)?
1 2 3
| xorl %eax, %eax
mov $0, %eax
andl $0, %eax |
- 你可能想读这篇文章
- xor与mov:stackoverflow.com/questions/1135679/…
TL;DR总结:xor same, same是所有CPU的最佳选择。没有其他方法比它有任何优势,而且它至少比任何其他方法有一些优势。这是英特尔和AMD的官方推荐。在64位模式下,仍然使用xor r32, r32,因为写入32位寄存器0会使上32位变为零。xor r64, r64浪费了一个字节,因为它需要一个rex前缀。好的。
更糟糕的是,silvermont只将xor r32,r32识别为DEP中断,而不是64位操作数大小。因此,即使由于归零r8..r15而仍然需要rex前缀,也要使用xor r10d,r10d,而不是xor r10,r10。好的。
实例:好的。
1 2 3 4 5 6 7 8 9
| xor eax, eax ; RAX = 0
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
mov eax, 0 ; doesn't touch FLAGS, but not faster and takes more bytes |
调零矢量寄存器通常最好用pxor xmm, xmm来完成。这通常是GCC所做的(甚至在与fp指令一起使用之前)。好的。
xorps xmm, xmm是有意义的。它比pxor短一个字节,但xorps需要在Intel Nehalem上使用执行端口5,而pxor可以在任何端口(0/1/5)上运行。(Nehalem在integer和fp之间的2c旁路延迟通常不相关,因为无序执行通常会在新的依赖链开始时隐藏它)。好的。
在SNB家族的微体系结构中,XOR归零的味道都不需要执行端口。在AMD和Pre-Nehalem P6/Core2 Intel上,xorps和pxor的处理方式相同(与矢量整数指令相同)。好的。
使用128B矢量指令的AVX版本也会使寄存器的上部归零,因此vpxor xmm, xmm, xmm对于归零YMM(AVX1/AVX2)或ZMM(AVX512)或任何未来的矢量扩展都是一个不错的选择。但是,vpxor ymm, ymm, ymm不需要额外的字节来编码,并且运行相同的代码。AVX512 ZMM归零需要额外的字节(用于evex前缀),因此应首选xmm或ymm归零。好的。
有些CPU将sub same,same识别为一种归零习惯用法,如xor,但所有识别任何归零习惯用法的CPU都识别xor。只需使用xor,就不必担心哪个CPU识别哪个调零习惯用法。好的。
xor(与mov reg, 0不同,xor是公认的归零习语)有一些明显的和微妙的优势(总结清单,然后我将在这些方面进行扩展):好的。
- 比mov reg,0更小的代码大小。(所有CPU)
- 避免对以后的代码进行部分注册惩罚。(Intel P6系列和SNB系列)。
- 不使用执行单元,节省电源并释放执行资源。(英特尔SNB系列)
- 较小的UOP(无即时数据)会在UOP缓存线中留下空间,以便在需要时获取附近的指令。(英特尔SNB系列)。
- 不会用尽物理寄存器文件中的条目。(至少Intel SNB系列(和P4)也可能是AMD,因为他们使用类似的PRF设计,而不是像Intel P6系列微体系结构那样在Rob中保持注册状态。)
较小的机器代码大小(2字节而不是5字节)始终是一个优势:较高的代码密度导致较少的指令缓存未命中,以及更好的指令获取和潜在的解码带宽。好的。
在Intel SNB系列微体系结构上不使用XOR执行单元的好处很小,但可以节省电力。在只有3个ALU执行端口的SNB或IVB上更可能发生问题。haswell和后来的haswell有4个执行端口,可以处理整数的ALU指令,包括mov r32, imm32,因此,通过调度程序的完美决策(实际上没有),即使HSW都需要执行端口,它仍然可以支持每时钟4个Uops。好的。
请参阅我对另一个关于调零寄存器的问题的回答,了解更多详细信息。好的。
布鲁斯·道森在迈克尔·佩奇链接的博客中(在对这个问题的评论中)指出,xor是在注册重命名阶段处理的,不需要执行单元(在未使用的域中为零Uops),但忽略了它仍然是融合域中的一个Uops这一事实。现代Intel CPU每时钟可发出和注销4个融合域UOP。这就是每个时钟限值4个零的来源。寄存器重命名硬件的复杂性增加只是将设计宽度限制为4的原因之一。(布鲁斯写了一些非常优秀的博客文章,比如他的关于FP数学和X87/SSE/舍入问题的系列文章,我强烈推荐)。好的。
在AMD推土机系列CPU上,mov immediate运行在与xor相同的ex0/ex1整数执行端口上。mov reg,reg也可以在agu0/1上运行,但这只用于寄存器复制,而不用于立即设置。因此,在AMD上,与xor相比,mov的唯一优势是编码更短。它还可以节省物理注册资源,但我没有看到任何测试。好的。
公认的归零习惯用法避免了英特尔CPU上的部分寄存器惩罚,后者将部分寄存器与完整寄存器(p6&snb系列)分开重命名。好的。
xor将把寄存器的上半部分标记为零,因此xor eax, eax/inc al/inc eax避免了以前的IVB CPU通常的部分寄存器惩罚。即使没有xor,当修改高位8位(AH时,ivb也只需要一个合并Uop,然后读取整个寄存器,而haswell甚至删除了它。好的。
摘自Agner Fog的Microarch指南,第98页(Pentium M部分,后面部分包括SNB参考):好的。
The processor recognizes the XOR of a register with itself as setting
it to zero. A special tag in the register remembers that the high part
of the register is zero so that EAX = AL. This tag is remembered even
in a loop:
Ok.
1 2 3 4 5 6 7 8 9 10
| ; 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 |
(from pg82): The processor remembers that the upper 24 bits of EAX are zero as long as
you don't get an interrupt, misprediction, or other serializing event.
Ok.
该指南的第82页也证实了mov reg, 0不被认为是归零习语,至少在早期的p6设计中,如piii或pm。如果他们把晶体管花在后来的CPU上检测,我会非常惊讶的。好的。(P)EDOCX1 0 silian sets flags,which means you have to be careful when testing conditions.由于EDOCX1的英文本是不及时的,只有在一个8比特的目的地可供使用,你通常需要注意避免具体的刑事登记。好的,好的。(P)如果X86-64被重新命名的OPCodes(Like AAM)中有一个是16/32/64 Bit EDOCX1的英文字母2,而且传道者在R/M外勤地的来源地——登记处3-比特地(The way some other single-operation instructions use the m as OPCode bits),那么它将是一个独特的例子。但是,他们没有说,而且他们不会帮助X86-32任何方式。好的,好的。(P)理想主义者,你应该使用0/set flags/EDOCX1,1/read full register:好的,好的。字母名称(P)这是所有CPUS(不是Stalls,Merging Uops,就是fals dependencies)的最佳表现。好的,好的。(P)当你不想在发出指示之前就这么做的时候,事情就更加复杂了。E.G.You want to branch on on e condition and then setcc on another condition from the same flags.E.G.EDOCX1(英文)5,EDOCX1(英文)6,而你却没有斯巴达登记册,或你想保留EDOCX1(英文)0,而不采用适当的编码。好的,好的。(P)There are no recognized zeroing idions that don't affect flags,so the best choice depends on the target microarchitecture.On Core2,插入Merging Uop might cause 2 or 3 cycle stall.It appears to be cheaper on SNB,but I did't spend much time trying to measure.Using EDOCX1 original 8/EDOCX1 penalty 1 communal would have a significant penalty on older intel cpus,and still be some worse on newer intel.好的,好的。(P)1.Using EDOCX1 original 1/EDOCX1/Americano 11 is probably the best alternative for intel P6&;SNB families,if you can't Xor-Zero ahead of the flag-setting instruction.在Xor-Zeroing之后,最好不要重复试验。(Don't even consider EDOCX1 plus 12 welcx1/EDOCX1 plus 13 welcx1 plus 14 welcx1)IVB can eliminate EDOCX1 pental 11 silian(I.E.handle it with register-renaming with no execution unit or postacy,like Xor-Zeroing).Haswell and later only eliminate regular EDOCX1 indications,so EDOCX1 original 18.Takes an execution unit and has non-Zero潜伏,making test/EDOCX1 universal/EDOCX1/EDOCX1 indicative 18 worse than EDOCX1 indicative 0/test/EDOCX1/EDOCX1,but still at least as good test/EDOCX1/EDOCX1好的,好的。(P)1.Using EDOCX1 original/EDOCX1 plus 18 with no Zeroing first is bad on AMD/P4/Silvermont,because they don't track deps separately for sub-registers.这将是一个错误的记录,以旧的登记价值为依据。1.Using EDOCX1 original 8/EDOCX1 plus 1 for zeroing/dependency-breaking is probably the best alternative when n EDOCX1 penal 0/test/EDOCX1好的,好的。
当然,如果您不需要setcc的输出大于8位,则不需要将任何内容归零。但是,如果您选择了一个最近成为长依赖链一部分的寄存器,那么要注意对p6/snb以外的CPU的错误依赖。(如果调用的函数可能会保存/恢复正在使用的寄存器的一部分,请注意导致部分寄存器暂停或额外的UOP。)好的。
即时为零的and并不是特殊情况,因为它独立于我所知道的任何CPU上的旧值,所以它不会破坏依赖链。它与xor相比没有优势,也有许多缺点。好的。
请参阅http://agner.or g/optimize/了解微搜索文档,包括哪些归零习惯用法被认为是断开依赖关系(例如,sub same,same在某些CPU上,而不是所有CPU上,而xor same,same在所有CPU上都被识别)。mov不会断开寄存器旧值上的依赖链(无论源值是否为零,因为这就是mov的工作原理。xor只在src和dest是同一个寄存器的特殊情况下断开依赖链,这就是为什么mov被排除在特殊识别的依赖断开器列表之外的原因。(还有,因为它不被认为是一个归零的习语,还有其他好处。)好的。
有趣的是,最古老的p6设计(通过Pentium III的ppro)没有将xor调零识别为依赖性中断,只是为了避免部分寄存器暂停而将其作为调零习惯用法,因此在某些情况下,这两种方法都值得使用。(参见Agner Fog的示例6.17。在他的微创PDF中。他说这也适用于p2,p3,甚至(早期?)下午。链接博客帖子上的一条评论说,只有PPRO有这种疏忽,但我在katmai piii上进行了测试,在Pentium M上测试了@fanael,我们都发现它没有打破对延迟限制的imul链的依赖。)好的。
如果它真的使代码更好或保存了指令,那么当然,只要不引入代码大小以外的性能问题,就可以使用mov归零以避免触摸标志。不过,避免删除标志是不使用xor的唯一合理原因。好的。好啊。
- 型有趣。所以它不是真正的100%免费。我的意思是,即使它不使用端口,它仍然需要一个微操作。这是我在阿格纳手册中遗漏的一个微妙之处。谢谢!所以它的延迟为零,但吞吐量为4(或0.25倒数吞吐量)。
- 型大多数算术指令op r,s被无序的CPU强制等待寄存器r的内容被以前的以寄存器r为目标的指令填充;这是数据依赖性。关键是,当遇到XOR,R时,Intel/AMD芯片有特殊的硬件需要中断,必须等待寄存器R上的数据依赖性,而对于其他寄存器归零指令则不一定如此。这意味着XOR指令可以被调度为立即执行,这就是Intel/AMD建议使用它的原因。
- 型@irabaxter:是的,为了避免混淆(因为我已经看到了这样的误解),mov reg, src也破坏了面向对象CPU的DEP链(不管src是imm32、[mem]或其他寄存器)。这种依赖性破坏并没有在优化手册中提到,因为它不是一种特殊情况,只有当src和dest是同一个寄存器时才会发生。它总是发生在不依赖其目的地的指令上。(除了Intel在dest上使用假dep的popcnt/lzcnt/tzcnt的实现。)
- 型@zboson:没有依赖关系的指令的"延迟"只有在管道中出现气泡时才重要。MOV消除的好处很好,但是对于零指令,零延迟的好处只在诸如分支预测失误或I$Miss之类的情况下起作用,在这种情况下,执行是等待解码的指令,而不是等待数据准备就绪。但是,mov消除并不能使mov免费,只有零延迟。"不接受执行端口"部分通常不重要。融合域的吞吐量很容易成为瓶颈,特别是在混合负载或存储的情况下。
- 型根据Agner的说法,knl不承认64位寄存器的独立性。所以xor r64, r64不只是浪费一个字节。如你所说,埃多克斯1〔5〕是最好的选择,尤其是对KNL。如果您想了解更多信息,请参阅本手册第15.7节"特殊独立案例"。
- 型@Zboson:在Agner的更新中看到这个答案之后,我已经开始为这个答案进行knl更新:需要指出的是,r32很重要,即使它没有保存rex前缀(xor r8d,r8d)。但后来我得到了侧跟踪设置我的新Skylake i7-6700k桌面与16g的ddr4-2666 ram:)位从65nm core2duo升级,例如8x更快的视频编码与x264(这有很好的调优/优化core2,不同于x265)。我很快就会回到那个编辑,因为我仍然保存了文本。
- 型我很快就会得到一个天湖系统。真无聊!好吧,如果你是从core2duo来的。但现在我有了KNL。由于缺少AVX512,我对Skylake非常失望。所以我有一段时间不再考虑x86 simd了。我知道Skylake在内部会显著改变Broadwell上的管道(所以我听说了),即使指令集没有改变,但仍然如此。
- 型@Zboson:是的,随着UOP缓存读取带宽的增加和旧的解码吞吐量的增加,前端气泡应该更加罕见。(但4-Uop/时钟前端最大值仍然相同)。在更多端口上运行更多指令可能非常酷。如果我想在skl之前把代码调到最近的intel上,我担心skl会避免hsw/bdw仍然存在的瓶颈。/我考虑过购买更差的硬件:p如果你还有什么要说的话,我们应该用它来聊天。(我不是自动取款机)。但我得看看如何在评论内容不提供选项时创建聊天。
- 型如果你想为skl和其他arch进行优化,我将研究使用gcc的函数多版本化(fmv)。参见phoronix.com/forums/forum/software/distributions/…
- @zboson:我的意思是我在skl上测试/调优的代码可能无法避免hsw或snb的性能缺陷,不管指令集选择如何。问题不在于如何为hsw和skl制作不同的版本,而是如何在不测试skl之前的硬件的情况下调整hsw版本。
- 好吧,我明白你的意思了。
- 这里有一个例子,即使是最新的gcc,也有些愚蠢地为一个简单的函数发布了mov reg, 0,而不是xor。当然,这可能是因为它需要保留早期cmp的标志,但它可能只是交换了订单!clang做得很好,icc也使用xor但只得到部分标记,因为它在关键路径中毫无意义地包括mov esi, esi。
- @是的,海湾合作委员会有时会做这种傻事。我想知道是不是为了保存一个登记册,以备cmp不能延期的案件使用。(例如,如果它想导致esi)。在GCC使代码变得更糟的其他情况下(如setcc/movzx,而不是xor/setcc,它通常看起来像是一个用来减少注册压力的习语,即使没有。
- 是的,也许对于寄存器压力没有反馈机制:它在生成代码时使用一些"典型"的权衡,然后当它到达函数的末尾时,它不会返回并放松可能存在寄存器压力的假设。
- @是的,没错。我不确定它的一些习惯用法是如何"屏蔽"的,但我认为编译器更容易将setcc/movzx作为一个单独的东西来处理,而不是在函数的内部表示中添加一些东西来表示"好的,在设置标志之前我们需要一个xor归零的寄存器",如果这很难做到,可能还有一个回退。(尽管在大多数情况下,你会期望它最终会重做一个test或cmp)。
- 啊,好的旧MIPS在哪里呢,需要的时候有它的"零寄存器"。
- 当寄存器值已知时,有没有一种方法可以用负熵(较少热量)来实现这一点?"负熵的热力学意义"arxiv-vanity.com/papers/1009.1630
- @Westburner:不像英特尔SandyBridge系列那样,使用CMOS逻辑的常规x86 CPU。en.wikipedia.org/wiki/cmos电源:转换和漏电。运行调零惯用指令与在snb系列上运行nop指令的功率差不多,比mov立即或常规xor便宜,可能比pause以外的任何其他指令都要少。但仍然比坐在低能量睡眠中要多得多。现代数字逻辑离信息理论上每一次计算的能量极限很远,而且它们在内部所做的任何事情都没有负成本。
- 当你撞上setcc时,我完全失去了你。这跟归零有什么关系?这是如何添加到xor习语中的?
- @彼得卡兹"见Agner Fog的例子6.17。在他的微创PDF中。他声称这也适用于p2、p3甚至(早期?)但是我对此持怀疑态度。在链接博客帖子上的一条评论说,只有PPRO有这种监督。P6家族的多代人似乎不太可能在不识别XOR归零作为DEP断路器的情况下存在。"我在Tualant Pentium III和Dothan Pentium M上测试了它,在10&215;imul eax, eax/xor eax, eax上循环,理由是如果xor是DEP断开,那么如果它是DEP断开,那么循环将受到吞吐量限制和延迟限制。OT…
- …在这些CPU上,结果是明确的:每22条指令(即一次迭代)有50个周期,这表明有一个清晰的依赖链;在更现代的CPU上,如果xor是依赖中断的,则每22条指令比较10个周期。很明显,Agner在这里是正确的,因为xor不是对Pentium II/III和Pentium M的依赖性破坏。它可能在Yonah中发生了变化,上一代Pentium M作为Core Solo和Core Duo(注:不是Core 2)销售,但我没有要测试的硬件。
- @法纳尔:谢谢,我应该在不久前更新这个。我查看了一个katmai piii,发现它不是dep打破前一段时间,但从未完成编辑更新。现在做了一个修复我遗漏的两个主要问题。