AMD64汇编程序:if- Statement

时间:2014-03-26 09:44:42

标签: if-statement assembly x86 x86-64

我有两个8位寄存器,如果其中一个是0,则必须检查。

我现在的解决方案是:

cmp $0, %r10b
je end
cmp $0, %r11b
je end

还有其他办法吗?

问候

2 个答案:

答案 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是可预测的,但是ab实际上为零是不可预测的,这可以减少分支误预测的数量。尽管如此,基准/性能计数器测试它是:据说程序员在猜测哪些分支在代码中是可预测的时候是出了名的。 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,然后jzjnz,甚至cmov