为什么Clang只从Sandy Bridge开始做这个优化技巧?

phu*_*clv 7 optimization assembly x86-64 division clang

我注意到Clang为以下片段做了一个有趣的部门优化技巧

int64_t s2(int64_t a, int64_t b)
{
    return a/b;
}
Run Code Online (Sandbox Code Playgroud)

如果指定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
Run Code Online (Sandbox Code Playgroud)

以下是签名版本未签名版本的Godbolt链接

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

我检查了这个表,看到Core2和Nehalem上32/64位分区的延迟分别为40/116和26/89.因此,如果操作数确实通常不宽,那么通过执行32位除法而不是64位除法的节省可能与SnB一样值得

那么为什么它仅适用于SnB和后来的微体系结构?为什么GCC或ICC等其他编译器不这样做呢?

Pet*_*des 5

我猜测clang开发者测试了哪些是好的,并发现它只是SnB家族.

这听起来是正确的,因为P6系列上有一个时髦的档位,以及AMD不同的分频器.


使用来自P6系列上的移位imm8(不是按隐式移位1)的标志结果导致前端在发出标志读取指令之前停止,直到移位退出为止.(因为P6解码器没有检查imm8 = 0的情况,因为没有修改标志,而SnB会这样做). INC指令与ADD 1:重要吗?.这可能就是为什么clang不会将它用于P6系列的原因.

可能是一种不同的方式来检查不会导致这种失速的相关条件(就像test rcx,rcx之前的那样je,会让它在Core2/Nehalem上值得). 但是如果clang开发人员没有意识到它在P6系列上的速度很慢的原因,那么他们就不会想到修复它,而只是让它没有为SnB之前的目标做好.(不幸的是,没有人把我添加到补丁评论或关于这个的错误CC列表中;这是我第一次看到clang做这个优化.虽然我想我可能已经在其他一些LLVM审查的评论中提到了移位标志档或无论如何,尝试添加一个可能很有趣test,看看是否值得在Nehalem.)


根据Agner Fog的说法,AMD的分频器具有相同的最佳情况div性能,无论操作数大小如何,可能仅取决于输入的实际大小.只有最坏情况会随着操作数大小而增长. 所以我认为idiv r64在AMD上使用符号扩展到128/64位的小输入运行是无害的. (对于所有操作数大小,AMD上的div/idiv是2 uop(8位除外,因为它只需要写一个输出寄存器:AH和AL = AX.与Intel的微编码整数除法不同.)

英特尔是非常不同的:在Haswell上idiv r32是9 uops,idiv r64而59 uops,最佳情况下的吞吐量是3倍.SnB系列的其他成员也很相似.

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

可能是因为clang开发人员想到了它,而gcc/icc还没有复制它们.如果你看过Chandler Carruth的谈话perf,他用过的一个例子就是玩一个分支跳过a div.我猜这个优化是他的想法.看起来很漂亮.:)