请考虑以下代码:
#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更好地适应现代英特尔CPU?
使用-march=haswell
,它会发出BMI2 shlx / shrx,但仍然没有shrd。
答案 0 :(得分:6)
不,我看不到让gcc使用SHRD
指令的方法
您可以通过更改-mtune
and -march
选项来操纵gcc生成的输出。
或者是否有任何gcc -mtune或其他选项可以鼓励gcc更好地调整现代Intel CPU?
是的,你可以让gcc生成BMI2 code:
例如:X86-64 GCC6.2 -O3 -march=znver1 //AMD Zen
生成:( Haswell时间)。
code critical path latency reciprocal throughput
---------------------------------------------------------------
mov eax, 32 * 0.25
sub eax, edx 1 0.25
shlx eax, esi, eax 1 0.5
shrx esi, edi, edx * 0.5
or eax, esi 1 0.25
ret
TOTAL: 3 1.75
与clang 3.8.1相比:
mov cl, dl 1 0.25
shrd edi, esi, cl 4 2
mov eax, edi * 0.25
ret
TOTAL 5 2.25
鉴于这里的依赖关系链:Haswell上的SHRD
速度较慢,与Sandybridge相关,Skylake的速度较慢。
shrx
序列的倒数吞吐量更快。
所以这取决于,在后BMI处理器上gcc产生更好的代码,BMI之前的胜利
SHRD
在不同的处理器上有不同的时序,我可以看出为什么gcc并不过分喜欢它。
即使使用-Os
(针对大小进行优化),gcc仍然不会选择SHRD
。
*)不是时序的一部分,因为它们不在关键路径上,或者变成零延迟寄存器重命名。