【发布时间】:2016-09-01 22:42:56
【问题描述】:
考虑以下代码:
#include <limits>
#include <cstdint>
using T = uint32_t; // or uint64_t
T shift(T x, T y, T n)
{
return (x >> n) | (y << (std::numeric_limits<T>::digits - n));
}
根据godbolt,clang 3.8.1为-O1、-O2、-O3生成如下汇编代码:
shift(unsigned int, unsigned int, unsigned int):
movb %dl, %cl
shrdl %cl, %esi, %edi
movl %edi, %eax
retq
虽然 gcc 6.2(即使使用 -mtune=haswell)生成:
shift(unsigned int, unsigned int, unsigned int):
movl $32, %ecx
subl %edx, %ecx
sall %cl, %esi
movl %edx, %ecx
shrl %cl, %edi
movl %esi, %eax
orl %edi, %eax
ret
这似乎远没有优化,因为SHRD is very fast on Intel Sandybridge and later。是否有重写函数以方便编译器(尤其是 gcc)优化并支持使用 SHLD/SHRD 汇编指令?
或者是否有任何 gcc -mtune 或其他选项可以鼓励 gcc 更好地针对现代 Intel CPU 进行调整?
使用-march=haswell,它会发出 BMI2 shlx / shrx,但仍然不会 shrd。
【问题讨论】:
-
其实差别很小。
shrd需要 4 个周期才能解决。sal需要 2 个。我的猜测是 gcc 需要 7 个周期,而 clang 需要 5 个。(Skylake)在例如Bulldozer gcc 更快,因为sal/shr是单循环,shrd是 8。 -
@Johan:Haswell:SHRD 是 1uop,3c 延迟,每 1c 吞吐量一个。
SHL/SHR r,cl是 3 uop,2c 延迟,每 2c 吞吐量一个。我忘记了是否可以在寄存器重命名时消除 clang 愚蠢的 8 位 mov,所以 clang 的代码在 SKL 上是 4c 或 3c 延迟,具有更多更好的吞吐量。 -
@Johan:哎呀,我在看
shrd r,r,i,而不是shrd r,r,cl。可变计数版本仍然是 4 uop,具有 4c 延迟,并且在 BMI2 可用时不是最佳选择。 -
@Johan:我总是查看已经存在的电子表格版本(
.ods格式,OpenOffice,但如果需要,您可以轻松地将其转换为 Excel)。我的错误是我记得专门在我自己的 SnB 硬件上测试 SHRD,并且它在 SnB 上是有效的(并且 IACA 对 SnB 上的 SHRD/SHLD 是错误的)。但我记得的是即时计数版本,所以我只看到了我期望在 Agner 的电子表格中看到的内容。 :// -
我认为值得指出的是,n 为 0 是未定义的行为。
标签: c++ gcc assembly optimization bit-shift