最少 uops(前端带宽):
1个uop,延迟3个时钟周期(英特尔)或1个时钟周期(Zen)。
同时最小的代码大小为5个字节。
popcnt %rax, %rax
大多数CPU上,如果有的话,它的延迟是3个时钟周期,吞吐量为1个时钟周期(一个端口的1个微操作)。或者在Zen1/2/3上为1个时钟周期,吞吐量为0.25个时钟周期。在Excavator之前的Bulldozer系列中,
popcnt r64
的延迟为4个时钟周期,吞吐量为4个时钟周期(32位操作数大小的吞吐量为2个时钟周期,但仍然是4个时钟周期的延迟)。Bobcat的微码popcnt非常慢。 (
https://agner.org/optimize/&
https://uops.info/)
最低延迟(假设使用Haswell或更新版本,因此在写入AL并读取EAX时没有部分寄存器效应,或者具有不重命名部分寄存器的无P6祖先的微架构):
2个周期延迟,2个uop,6个字节。如果popcnt(5B)不可用,则也是最小的代码大小。
test %rax, %rax # 3B, 1 uop any ALU port
setnz %al # 3B, 1 uop p06 (Haswell .. Ice Lake)
# only works in-place, with the rest of EAX already being known 0 for RAX==0
AL是EAX的低字节,所以对于任何非零的RAX,AL=1都会使EAX非零。
在Sandybridge/Ivy Bridge上读取EAX时,这将花费一个合并uop。Core2/Nehalem会停顿几个周期来插入该合并uop。早期的P6系列处理器(如Pentium-M)将完全停顿,直到稍后的指令读取EAX为止。(为什么GCC不使用部分寄存器?)
Nate的neg/sbb在Broadwell及更高版本上与此相同,但长度缩短了1个字节。(并将上32位清零)。在Haswell上效果更好,因为sbb
需要2个uops。在早期的主流Intel CPU上,它们都需要3个uops,其中当读取EAX时,这个需要一个合并uop,而sbb
(除了在SnB/HSW上的sbb $0
)总是需要2个uops。neg/sbb可以用于不同的寄存器中(仍然破坏输入),但在除AMD之外的CPU上存在虚假依赖。(K8/K10、Bulldozer-family和Zen-family都将sbb same,same
识别为仅依赖于CF)。
如果您想将前32位清零,可以使用BMI2
RORX 进行复制和移位:
2个微操作,延迟2c,8字节
rorx $32, %rax, %rdx # 6 bytes, 1 uop, 1c latency
or %edx, %eax # 2 bytes, 1c latency
## can produce its result in a different register without a false dependency.
rorx $32
通常用于水平 SWAR 缩减,例如对于 dword 水平求和,您可以使用 movq
将一对 dwords 从 XMM 寄存器中移出,并在标量中使用 rorx/add 进行最后一个 shuffle+add,而不是使用 pshufd/paddd。
或者在没有 BMI2 的情况下仍然将上 32 位清零:
7 字节,4 uops,在 Intel 上的延迟为 3c(其中 bswap r64
是 2 uops,2c 延迟),否则在具有高效 bswap r64
的 CPU 上的延迟为 3 uops 2c,例如 Zen-family 和 Silvermont-family。
mov %eax, %edx # 2 bytes, and not on the critical path
bswap %rax # 3 bytes, vs. 4 for shr $32, %rax
or %edx, %eax # 2 bytes
## can write a different destination
使用
shr $32, %rax
代替
bswap
的妥协方案是:
8字节,3个微操作,2个时钟周期的延迟,即使没有mov-elimination也是如此。
在原始寄存器上运行ALU指令而不是
mov
结果可以让未消除的MOV与之并行运行。
评估“最佳”性能的背景:
未成功的想法:
bsf %rax, %rax
当输入为0时,不更改目标。(AMD文档中有记录,Intel实现了但没有将其记录在未来的保护中。) 不幸的是,这将导致输入为1时RAX=0。
在Intel上与popcnt
性能相同,在AMD上性能更差,但可以节省1个字节的代码大小。
(使用sub $1
设置CF,然后??;Nate使用neg
的方法可以使其工作得更加清晰。)
我还没有尝试使用superoptimizer强制检查其他可能的序列,但正如Nate所评论的那样,这是一个足够短小的问题,可以成为一种用例。
crc32 rax, rax
可能是一个选项 :-) - Alex Gutenievneg rax
替换cmp rax, 1
可以得到正确的结果,而且代码也更短。请看我的回答。 - Nate Eldredge