我有两个8位寄存器,如果其中一个是0,则必须检查。
我现在的解决方案是:
cmp $0, %r10b
je end
cmp $0, %r11b
je end
还有其他办法吗?
问候
答案 0 :(得分:1)
本答案中的性能讨论针对最近的英特尔CPU(Sandybridge,Haswell)。至少早在Pentium M,甚至更早的P6(Pentium Pro / Pentium II)上都适用。有关微文档文档,请参阅http://agner.org/optimize/。 AMD的性能考虑因素应该类似,不过它们不会将测试和分支指令宏观融合到单个宏操作中,就像英特尔宏将它们融合到单个uop中一样。
分支预测器存在于每个流水线设计中,但对于像Haswell这样的东西比旧的Silvermont Atom更重要。不过,这部分非常普遍。
对您的版本进行小调整:
test %r10b, %r10b ; test is shorter than cmp with an immediate, but no faster
jz end
test %r11b, %r11b
jz end
可能只有test
/ jz
对中只有一对会在英特尔上进行宏观融合,因为它们可能都会在同一周期中击中解码器。此外,如果任一值是ALU op的输出,它可能已经设置了零标志。因此,请安排您的代码,以便其中一个分支不需要单独test
。
您可以保存分支(以额外的uop为代价)。甚至非采用分支的吞吐量可能是一个非常紧密的循环的瓶颈。 Sandybridge每1-2个循环只能维持1个分支。所以这个想法可能有助于:
test %r10b, %r10b
setnz %r15b # 1 if %r10b == 0, else 0
dec %r15b # 0 if %r10b == 0, else 0xFF
test %r11b, %r15b
je end
这是另外一条指令(所有单指令指令都有1个周期延迟。)它会在分支指令退出之前增加更多延迟(将误预测惩罚增加3个周期),但它可以提高性能: / p>
如果a && b
是可预测的,但是a
或b
实际上为零是不可预测的,这可以减少分支误预测的数量。尽管如此,基准/性能计数器测试它是:据说程序员在猜测哪些分支在代码中是可预测的时候是出了名的。 CPU具有有限大小的分支历史缓冲区,因此使用少一个条目可以帮助一点点。
如果延迟并不重要,那么只是吞吐量(即错误预测很少):
# mov %r10b, %al # have the byte you want already stored in %r10b
imul %r11b # Intel: 3 cycle latency, 1/cycle throughput.
# ZF is undefined, not set according to the result, unfortunately
test %ax, %ax # No imm16, so no Intel length-changing-prefix stall for a 16bit insn
je .end
总共2次uop(测试/ je可以宏观融合,甚至在AMD上)。如果你需要保存%al的旧值,或者你不能免费获得%al中的一个值,那么这是一个额外的mov。
如果寄存器的高位字节为零,则可能获得速度:如果使用字节操作将字节值输入寄存器,则使用imul %r10d, %r11d
会产生部分寄存器停顿(或额外的uop)合并)。如果您编写了完整的32位寄存器(例如movzx
),则可以使用imul
的2操作数形式,并测试32位结果。 (上面的16将全部为零,这很好。)没有imul r8, r8
的2操作数形式,无论如何你需要一个完整的16b结果,因为它没有根据结果。如果是,则可能存在比较指令,该指令测试了零和进位或溢出标志的正确组合。手册说在imul
之后未定义ZF,所以不要依赖当前CPU发生的事情。这是做 need the upper bytes to be zero的一种情况。
使test %ax, %ax
在16位寄存器上运行的操作数大小前缀不应导致Intel上的解码停顿,因为它不会改变指令的 rest 的长度。可怕的LCP失速发生在16位即时,如test $0xffff, %ax
,所以除非你只针对AMD,否则请避免使用。
@Brett Hale对OP的评论:如果你的分支指令依赖于你,你只会获得部分标记停顿(或者在后来的CPU上,添加一个额外的uop以合并标志(很多更高效))标志位未被最后一条设置标志的指令修改。
答案 1 :(得分:0)
您可以先and
然后再做一个test
吗?或者您也可以尝试按照@Peter Cordes的建议进行乘法,但不是使用imul
,而是执行lea
?
但我建议您保留当前的代码,只需使用test
代替cmp
,然后对其进行模糊处理。
实际上,由于test
执行and
,只需在两个寄存器之间执行test
,然后jz
或jnz
,甚至cmov
。