使用内在函数时,为什么生成的程序集会重新排序?

时间:2014-06-08 00:36:06

标签: c gcc x86 sse intrinsics

我正在玩内在函数,因为我需要一个类似于O (1)的{​​{1}}复杂度函数来获得固定的输入大小。我最后写了这个:

memcmp()

,编译时会变成这样:

#include <stdint.h>
#include <emmintrin.h>

int64_t f (int64_t a[4], int64_t b[4]) {
    __m128i *x = (void *) a, *y = (void *) b, r[2], t;
    int64_t *ret = (void *) &t;

    r[0] = _mm_xor_si128(x[0], y[0]);
    r[1] = _mm_xor_si128(x[1], y[1]);
    t = _mm_or_si128(r[0], r[1]);


    return (ret[0] | ret[1]);
}

http://goo.gl/EtovJa(Godbolt Compiler Explorer)


在那之后,我很好奇我是否真的需要使用内在函数或者我是否只需要类型而我可以使用普通运算符。然后我修改了上面的代码(实际上只有三条SSE线)并最终得到了这个:

f:
    movdqa  xmm0, XMMWORD PTR [rdi]
    movdqa  xmm1, XMMWORD PTR [rdi+16]
    pxor    xmm0, XMMWORD PTR [rsi]
    pxor    xmm1, XMMWORD PTR [rsi+16]
    por xmm0, xmm1
    movq    rdx, xmm0
    pextrq  rax, xmm0, 1
    or  rax, rdx
    ret

而是编译成这个:

#include <stdint.h>
#include <emmintrin.h>

int64_t f (int64_t a[4], int64_t b[4]) {
    __m128i *x = (void *) a, *y = (void *) b, r[2], t;
    int64_t *ret = (void *) &t;

    r[0] = x[0] ^ y[0];
    r[1] = x[1] ^ y[1];
    t = r[0] | r[1];


    return (ret[0] | ret[1]);
}

http://goo.gl/oDHF3z(Godbolt Compiler Explorer)


现在功能上(AFAICT),两个编译的汇编输出是相同的。事实上,他们似乎甚至会花费相同的时间和资源;他们会执行相同的。但是,我很好奇为什么前四个指令中的操作数已被移动。是否有某种特殊原因可以解释为什么可以采用另一种方法?

注意:两个函数都是用GCC编译的,标记相同。

1 个答案:

答案 0 :(得分:3)

TL; DR :从编译器的角度来看,输入代码是不同的,可能会经过不同的地方并在途中遇到不同的测试,这会使输出不同。 / p>

你不会在(当前的)clang中看到这个,因为当你到达IR(LLVM使用的代码的中间表示)时,内在函数会消失,并且IR最终会转换为指令,但IR对于这两种情况都是一样的。

如果您使用clang或不同版本的gcc检查该代码,您会发现指令调度略有变化。这些更改通常是由于CPU调度程序或寄存器分配器(从版本到版本)的更改。

Try this out,包含您在同一文件中提供的两个功能。尝试不同版本的gcc,并尝试不同版本的clang。 Clang只改变了movd指令的顺序,它总是用相同的指令发出两个函数,因为llvm后端在两种情况下都得到相同的IR。

我不知道GCC的内部结构,但我认为这些函数碰巧没有达到调度程序代码中完全相同的位置,最终以不同的顺序发出负载。这可能是因为对内在函数的一个调用可能不会降低到一个案例的中间表示,而只是作为内在函数(而不是函数)调用。