han*_*rak 5 c x86 gcc sse intrinsics
我正在尝试一些内在函数,因为我需要一个O (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]);
}
Run Code Online (Sandbox Code Playgroud)
编译后会变成这样:
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
Run Code Online (Sandbox Code Playgroud)
http://goo.gl/EtovJa(Godbolt编译器资源管理器)
但在那之后,我开始好奇我是否真的需要使用内部函数,或者我是否只需要类型并且可以只使用普通运算符。然后我修改了上面的代码(实际上只有三行 SSE 行)并最终得到以下结果:
#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]);
}
Run Code Online (Sandbox Code Playgroud)
相反,编译为:
f:
movdqa xmm0, XMMWORD PTR [rdi+16]
movdqa xmm1, XMMWORD PTR [rdi]
pxor xmm0, XMMWORD PTR [rsi+16]
pxor xmm1, XMMWORD PTR [rsi]
por xmm0, xmm1
movq rdx, xmm0
pextrq rax, xmm0, 1
or rax, rdx
ret
Run Code Online (Sandbox Code Playgroud)
http://goo.gl/oDHF3z(Godbolt编译器资源管理器)
现在在功能上(AFAICT),两个编译后的汇编输出是相同的。事实上,他们甚至会花费完全相同的时间和资源;他们会以相同的方式执行。然而,我很好奇为什么前四个指令中的操作数被移动了。是否有某些特殊原因可以解释为什么可以采用一种方法而不是另一种方法?
注意:这两个函数都是用 GCC 编译的,具有相同的标志。
TL;DR:从编译器的角度来看,输入代码不同,可能会经过不同的地方并在途中遇到不同的测试,这会使输出不同。
您不会在(当前)clang 中看到这一点,因为当您到达 IR(LLVM 使用的代码的中间表示)时,内在函数就会消失,并且 IR 最终会转换为指令,但两种情况下的 IR是一样的。
如果您使用 clang 或不同版本的 gcc 检查该代码,您会发现指令调度发生了细微的变化。这些变化通常是由于不同版本的 CPU 调度程序或寄存器分配器的变化造成的。
使用您在同一文件中提供的两个函数来尝试一下。尝试不同版本的 gcc,并尝试不同版本的 clang。Clang 仅更改 movd 指令的顺序,并且它始终使用相同的指令发出两个函数,因为 llvm 后端在这两种情况下获得相同的 IR。
我不知道 GCC 的内部结构,但我想这些函数碰巧没有到达调度程序代码中完全相同的位置,最终以不同的顺序发出负载。可能会发生这种情况,因为在一种情况下,对内在函数的调用之一可能不会降低为中间表示,而只是保留为内在函数(而不是函数)调用。