为什么Clang仅从Sandy Bridge开始才执行此优化技巧?

时间:2019-01-30 10:33:10

标签: assembly optimization clang x86-64 division

我注意到Clang对以下代码段做了有趣的除法优化技巧

int64_t s2(int64_t a, int64_t b)
{
    return a/b;
}

如果将march指定为Sandy Bridge或更高版本,则下面是程序集输出

        mov     rax, rdi
        mov     rcx, rdi
        or      rcx, rsi
        shr     rcx, 32
        je      .LBB1_1
        cqo
        idiv    rsi
        ret
.LBB1_1:
        xor     edx, edx
        div     esi
        ret

以下是the signed versionthe unsigned version的Godbolt链接

据我了解,它检查两个操作数的高位是否为零,如果是,则进行32位除法

我检查了this table,发现Core2和Nehalem上32/64位除法的延迟分别为40/116和26/89。因此,如果操作数确实通常不宽,那么用32位除法而不是64位除法所节省的费用可能与SnB上的价值一样

那么为什么只对SnB和更高版本的微体系结构启用它?为什么其他编译器(例如GCC或ICC)不这样做?

1 个答案:

答案 0 :(得分:5)

我猜想clang开发人员测试了它的优缺点,并发现它只是SnB系列。

这听起来不错,因为P6家族的时髦档位以及AMD不同的分隔线。


使用P6系列的imm8移位(而不是隐式移位)产生的标志结果会导致前端在发出标志读取指令之前停滞,直到该移位退回。 (因为P6解码器不检查imm8 = 0的情况以使标志保持不变,而SnB则检查)。 INC instruction vs ADD 1: Does it matter?。这也许就是为什么clang不将其用于P6-系列的原因。

可能有一种不同的方法来检查不会导致此停顿的相关条件(例如,在test rcx,rcx之前的je在Core2 / Nehalem上是值得的)。 / strong>但是,如果clang开发人员没有意识到P6系列产品运行缓慢的原因,那么他们就不会考虑修复它,而只是将其用于SnB之前的目标。 (不幸的是,没有人将我添加到补丁审查或关于此漏洞的CC列表中;这是我第一次看到使用clang进行此优化。尽管我认为我可能在其他LLVM审查的评论中提到了shift标志停滞或bug。无论如何,尝试添加test并看看是否值得在Nehalem上使用它很有趣。)


Agner Fog表示,不管操作数的大小如何,AMD的分频器都具有相同的最佳情况下div的性能,大概仅取决于输入的实际大小。只有最坏的情况会随着操作数大小而增长。 因此,我认为使用少量输入将idiv r64运行在AMD上扩展为128/64位是无害的。(AMD上的div / idiv为2微秒对于所有操作数大小(8位除外,因为它只需要写入一个输出寄存器:AH和AL = AX,所以为8位除外。不同于Intel的微码整数除法。)

英特尔的差异非常大:idiv r32为9 oups,而idiv r64为59 oups,在Haswell上,最佳情况下的吞吐量要差3倍。 SnB家族的其他成员相似。

  

为什么不像GCC或ICC这样的其他编译器呢?

可能是因为clang开发人员想到了这一点,而gcc / icc尚未复制它们。如果您已经看过钱德勒·卡鲁斯(Chandler Carruth)关于perf的谈话,那么他使用的一个示例就是在一个分支上玩耍以跳过div。我猜这种优化是他的主意。看起来很漂亮。 :)