使g ++使用SHLD / SHRD指令

时间:2016-09-01 22:42:56

标签: c++ gcc assembly optimization bit-shift

请考虑以下代码:

#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。

1 个答案:

答案 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

*)不是时序的一部分,因为它们不在关键路径上,或者变成零延迟寄存器重命名。