要清除所有位,您经常会看到独占或XOR eax, eax
。反过来也有这样的伎俩吗?
我能想到的只是用额外的指令反转零。
答案 0 :(得分:14)
对于大多数具有固定宽度指令的体系结构,答案可能是一个无聊的指令mov
的符号扩展或反转立即,或mov lo / high对。例如在ARM上,mvn r0, #0
(move-not)。请参阅x86,ARM,ARM64和MIPS的gcc asm输出on the Godbolt compiler explorer。 IDK关于zseries asm或机器代码的任何内容。
在ARM中,eor r0,r0,r0
明显比mov-immediate差。它取决于旧值,没有特殊情况处理。内存依赖性排序规则prevent an ARM uarch from special-casing it even if they wanted to.对于大多数其他具有弱排序内存的RISC ISA也是如此,但memory_order_consume
不需要障碍(在C ++ 11术语中)。
x86 xor-zeroing因其可变长度指令集而特殊。
从历史上看,8086 xor ax,ax
直接快速因为它很小。由于习惯用法被广泛使用(并且归零比所有用户更常见),CPU设计人员给予了特殊支持,现在xor eax,eax
比英特尔Sandybridge系列和其他一些CPU上的mov eax,0
更快即使不考虑直接和间接的代码大小效果。请参阅What is the best way to set a register to zero in x86 assembly: xor, mov or and?,了解我能够挖掘的许多微观架构优势。
如果x86有一个固定宽度的指令集,我想知道mov reg, 0
是否会得到与xor-zeroing一样多的特殊处理?也许,因为写入low8或low16之前的依赖性破坏很重要。
最佳表现的标准选项:
mov eax, -1
:5个字节,使用mov r32, imm32
编码。 (不幸的是,没有符号扩展mov r32, imm8
)。所有CPU都具有出色的性能。 6个字节用于r8-r15(REX前缀)。mov rax, -1
:7个字节,使用mov r/m64, sign-extended-imm32
编码。 (不是eax
版本的REX.W = 1版本。那将是10字节mov r64, imm64
)。所有CPU都具有出色的性能。保存一些代码大小的奇怪选项通常以牺牲性能为代价:
xor eax,eax
/ dec rax
(或not rax
):5个字节(4个用于32位eax
)。缺点:前端有两个uops。在最近的英特尔上,调度程序/执行单元仍然只有一个未融合的域uop,前端处理xor-zeroing。 mov
- 立即需要执行单元。 (但整数ALU吞吐量很少是可以使用任何端口的指令的瓶颈;额外的前端压力是问题) xor ecx,ecx
/ lea eax, [rcx-1]
2个常量总共5个字节(rax
为6个字节):留下一个单独的归零寄存器< / strong>即可。如果您已经想要一个归零寄存器,那么这几乎没有任何缺点。在大多数CPU上,lea
可以在比mov r,i
更少的端口上运行,但由于这是新依赖关系链的开始,因此CPU可以在发出任何备用执行端口周期后运行它。
同样的技巧适用于任何两个附近的常量,如果你使用mov reg, imm32
执行第一个,而使用lea r32, [base + disp8]
执行第二个常量。 disp8的范围是-128到+127,否则你需要一个disp32
。
or eax, -1
:3个字节(rax
为4个),使用or r/m32, sign-extended-imm8
编码。缺点:对寄存器旧值的错误依赖。
push -1
/ pop rax
:3个字节。慢但很小。建议仅用于漏洞/代码高尔夫。 适用于任何sign-extended-imm8 ,与大多数其他人不同。
缺点:
rax
在Skylake执行后不会准备好约5个周期。rsp
时,将需要堆栈同步uop。 (例如add rsp, 28
或mov eax, [rsp+8]
)。将向量寄存器设置为带有 pcmpeqd xmm0,xmm0
的全向寄存器是特殊的,在大多数CPU上作为依赖性破坏(不是Silvermont / KNL),但仍需要执行单元来实际写入那些。 pcmpeqb/w/d/q
一切正常,但q
在某些CPU上速度较慢。
AVX / AVX2版本也是最佳选择。 Fastest way to set __m256 value to all ONE bits
AVX512 比较仅适用于屏蔽寄存器(如k0
)作为目标,因此编译器目前正在使用 vpternlogd zmm0,zmm0,zmm0, 0xff
作为512b all-one成语。 (0xff使3输入真值表的每个元素成为1
)。这不是特殊的,因为KNL或SKL上的依赖性破坏,但它在Skylake-AVX512上具有每时钟2个吞吐量。这比使用更窄的依赖性破坏AVX all-ones并广播或改组它更胜一筹。
如果需要在循环内重新生成all-one,显然最有效的方法是使用vmov*
复制all-one寄存器。这甚至不能在现代CPU上使用执行单元(但仍然需要前端问题带宽)。但是,如果您没有使用向量寄存器,则加载常量或[v]pcmpeq[b/w/d]
是不错的选择。
对于AVX512,值得尝试VPMOVM2D zmm0, k0
或VPBROADCASTD zmm0, eax
。每个都有only 1c throughput,但它们应该破坏zmm0旧值的依赖性(与vpternlogd
不同)。它们需要使用kxnorw k1,k0,k0
或mov eax, -1
在循环外部初始化的掩码或整数寄存器。
对于 AVX512掩码寄存器,kxnorw k1,k0,k0
可以正常工作,但它对当前CPU没有依赖性。 Intel's optimization manual建议在收集指令之前使用它来生成全1,但建议避免使用相同的输入寄存器作为输出。这避免了在循环中依赖于前一个的独立集合。由于k0
经常未被使用,因此通常是一个很好的选择。
我认为vpcmpeqd k1, zmm0,zmm0
可行,但它可能不是特殊的,因为k0 = 1成语而不依赖于zmm0。 (要设置所有64位而不是低16位,请使用AVX512BW vpcmpeqb
)
在Skylake-AVX512上,k
指令对掩码寄存器only run on a single port起作用,甚至是kandw
之类的简单指令。 (另请注意,当管道中有512b操作时,Skylake-AVX512不会在port1上运行向量uop,因此执行单元吞吐量可能是一个真正的瓶颈。)
没有kmov k0, imm
,只从整数或内存移动。可能没有k
指令相同,同样被检测为特殊指令,因此发布/重命名阶段的硬件不会为k
寄存器寻找它。
答案 1 :(得分:2)
彼得已经提供了一个完美的答案。我只想提一下,它也取决于上下文。
在某种情况下,我一次做过一个sar r64, 63
的数字,我知道该数字将为负,如果不是,则不需要所有位都设置值。 sar
的优点是它设置了一些有趣的标志,尽管确实对63
进行了解码,但是我也可以完成mov r64, -1
。我想那还是让我照做的旗帜。
因此,底线是:上下文。如您所知,您通常会深入研究汇编语言,因为您想处理自己而不是编译器所拥有的知识。也许某些不再需要其值的寄存器已经存储了1
(逻辑true
),然后只是存储了neg
。也许您在程序的较早位置做了一个loop
,然后(假设它是可管理的)可以安排寄存器的使用,因此not rcx
就是所缺少的。