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)
根据我的理解,它检查两个操作数的高位是否为零,如果是真的则进行32位除法
我检查了这个表,看到Core2和Nehalem上32/64位分区的延迟分别为40/116和26/89.因此,如果操作数确实通常不宽,那么通过执行32位除法而不是64位除法的节省可能与SnB一样值得
那么为什么它仅适用于SnB和后来的微体系结构?为什么GCC或ICC等其他编译器不这样做呢?
我猜测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.我猜这个优化是他的想法.看起来很漂亮.:)