移位操作后使用进位标志

时间:2018-10-18 11:59:26

标签: c++ assembly x86

对于以下代码的div / mod部分:

int pow(int x, unsigned int n)
{
    int y = 1;
    while (n > 1)
    {
        auto m = n%2;
        n = n/2;
        if (m)
            y *= x;
        x = x*x;
    }
    return x*y;
}

我希望组装像

shr n
cmovc y, yx

但是gcc / clang甚至icc在这里不使用进位标志(使用2个寄存器和and / test代替):https://godbolt.org/z/L6VUZ1

所以我想知道如果手动编码最好的方法是什么,为什么(ILP,依赖项...)。

1 个答案:

答案 0 :(得分:4)

test/je可以在主流的Intel和AMD CPU上宏融合为单个uop,因此在分支代码中,您仅节省代码大小,并通过使用CF输出的是班次,而不是先前的test/je

(不幸的是,这里的gcc确实很笨,它在test edx,edx的结果上使用and edx,1,而不是仅使用test dl,1或更佳的test sil,1保存{{1} mov也会使用imm32编码,因为test esi,1没有test r/m32, imm8编码,因此编译器知道要读取test的窄寄存器。)

但是在像clang使用这样的无分支实现中,是的,您可以通过使用test的CF输出来保存uop,而不用cmovc单独计算cmove的输入。您仍然不会缩短关键路径,因为testtest可以并行运行,并且主流的CPU(例如Haswell或Ryzen)具有足够的管道来充分利用所有ILP,而仅仅是{{ 1}}循环承载的依赖链。 (https://agner.org/optimize/)。

实际上,这是瓶颈,shr的{​​{1}}-> imul->下一迭代dep链。在Haswell及更早版本上,cmov是2个周期的延迟(2微秒),因此总dep链是2 + 3 = 5个周期。 (流水线乘法器意味着进行额外的imul乘法不会降低y部分的速度,反之亦然;它们可以一次飞行,而不必在同一周期内开始。)

如果您针对不同的基础反复使用相同的cmov,则分支版本应该可以很好地预测,并且非常好,因为分支预测+投机执行会分离数据依赖链。

否则,最好吃更长的无分支版本延迟时间,而不要遭受分支未命中的麻烦。