为什么 Clang 只从 Sandy Bridge 开始使用这种优化技巧?

Why does Clang do this optimization trick only from Sandy Bridge onward?

我注意到 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 version and the unsigned version

的 Godbolt 链接

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

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

一样有价值

那么为什么它只对 SnB 和后来的微架构启用?为什么 GCC 或 ICC 等其他编译器不这样做?

我猜 clang 开发人员测试了它适用于哪些 uarches,发现它只是 SnB 系列。

听起来不错,因为 P6 系列上的一个时髦的停顿,以及 AMD 的不同 dividers。


在 P6 系列上使用移位 imm8(不是隐式移位 1)的标志结果会导致前端在发出标志读取指令之前停止,直到移位 退休。 (因为 P6 解码器不检查 imm8=0 的情况以保留标志未修改,而 SnB 会检查)。 。这可能就是 clang 不将其用于 P6 系列的原因。

可能是一种不同的方法来检查不会导致此停顿的相关条件(比如 je 之前的 test rcx,rcx,在 Core2/Nehalem). 但如果 clang 开发者没有意识到它在 P6 系列上运行缓慢的原因,他们就不会想到修复它,而只是让它没有为 pre-SnB 目标完成。 (不幸的是,没有人将我添加到关于这个的补丁审查或错误 CC 列表中;这是我第一次看到 clang 进行这种优化。虽然我想我可能在其他一些 LLVM 审查或错误。无论如何,尝试添加一个 test 并看看这是否值得在 Nehalem 上使用可能会很有趣。)


根据 Agner Fog 的说法,

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

英特尔 非常 不同:idiv r32 是 9 微指令,而 idiv r64 是 59 微指令,最好的吞吐量是 3 倍,在哈斯韦尔。 SnB 家族的其他成员类似。

Why don't other compilers like GCC or ICC do it?

可能是clang开发者想到的,gcc/icc还没有抄袭。如果您看过 Chandler Carruth 关于 perf 的演讲,他使用的一个例子是玩弄一根树枝来跳过 div。我猜这种优化是他的主意。看起来很漂亮。 :)