我注意到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和the unsigned version的Godbolt链接
据我了解,它检查两个操作数的高位是否为零,如果是,则进行32位除法
我检查了this table,发现Core2和Nehalem上32/64位除法的延迟分别为40/116和26/89。因此,如果操作数确实通常不宽,那么用32位除法而不是64位除法所节省的费用可能与SnB上的价值一样
那么为什么只对SnB和更高版本的微体系结构启用它?为什么其他编译器(例如GCC或ICC)不这样做?
答案 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上使用它很有趣。)
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
。我猜这种优化是他的主意。看起来很漂亮。 :)