c - 为什么 "+="在 SSE 内在函数中给了我意想不到的结果

标签 c gcc sse intrinsics

sse内在有两种实现累加的方式。但其中一个得到了错误的结果。

#include <smmintrin.h>

int main(int argc, const char * argv[]) {

int32_t A[4] = {10, 20, 30, 40};
int32_t B[8] = {-1, 2, -3, -4, -5, -6, -7, -8};
int32_t C[4] = {0, 0, 0, 0};
int32_t D[4] = {0, 0, 0, 0};

__m128i lv = _mm_load_si128((__m128i *)A);
__m128i rv = _mm_load_si128((__m128i *)B);

// way 1 unexpected
rv += lv;
_mm_store_si128((__m128i *)C, rv);

// way 2 expected
rv = _mm_load_si128((__m128i *)B);
rv = _mm_add_epi32(lv, rv);
_mm_store_si128((__m128i *)D, rv);

return 0;
}

预期结果是:

9 22 27 36

C 是:

9 23 27 37

D 是:

9 22 27 36

最佳答案

在 GNU C 中,__m128i 被定义为 64 位 整数的 vector ,类似于

typedef long long __m128i __attribute__((vector_size(16), may_alias));

使用 GNU C native vector 语法(+ 运算符)对每个元素执行 64 位元素大小的加法。即_mm_add_epi64

在您的情况下,从一个 32 位元素顶部进行的进位会向其上方的 32 位元素添加一个额外的 1,因为 64 位元素大小确实会在成对的 32 位元素之间传播进位。 (向非零目标添加负数会产生进位。)


英特尔内在函数 API 没有为 __m128/__m128d/__m128i 定义 + 运算符。例如,您的代码无法在 MSVC 上编译。

因此,您获得的行为仅来自 GCC header 中内在类型的实现细节。它对于具有明显元素大小的浮点 vector 很有用,但对于整数 vector ,您需要定义自己的 vector ,除非您碰巧有 64 位整数。


如果您希望能够使用 v1 += v2; 您可以定义自己的 GNU C native vector 类型,例如

typedef uint32_t v4ui __attribute__((vector_size(16), aligned(4)));

请注意,我遗漏了 may_alias,因此只有将指针强制转换为 unsigned 才是安全的,而不是读取 char[] 等任意数据.

事实上,GCC 的 emmintrin.h (SSE2) 确实定义了一堆类型:

/* SSE2 */
typedef double __v2df __attribute__ ((__vector_size__ (16)));
typedef long long __v2di __attribute__ ((__vector_size__ (16)));
typedef unsigned long long __v2du __attribute__ ((__vector_size__ (16)));
typedef int __v4si __attribute__ ((__vector_size__ (16)));
typedef unsigned int __v4su __attribute__ ((__vector_size__ (16)));
typedef short __v8hi __attribute__ ((__vector_size__ (16)));
typedef unsigned short __v8hu __attribute__ ((__vector_size__ (16)));
typedef char __v16qi __attribute__ ((__vector_size__ (16)));
typedef unsigned char __v16qu __attribute__ ((__vector_size__ (16)));

我不确定它们是否供外部使用。

当您想让编译器发出有效的代码以除以编译时常量或类似的东西时,GNU C native vector 最有用。例如16 位无符号整数的 digit = v1 % 10;v1/= 10; 将编译为 pmulhuw 和右移。但它们对于可读代码也很方便。


有一些 C++ 包装器库可移植地提供运算符重载,并且具有诸如 Vec4i (4xsigned int)/Vec4u (4x unsigned int)/ 等类型Vec16c(16x 有符号字符)为您提供不同类型整数 vector 的类型系统,因此您知道从 v1 += v2;v1 > 中得到什么>= 2; (右移是符号性很重要的一种情况。)

例如Agner Fog 的 VCL(GPL 许可证)或 DirectXMath(MIT 许可证)。

关于c - 为什么 "+="在 SSE 内在函数中给了我意想不到的结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56572357/

相关文章:

c - 是否有可用的跨平台 C 信号库(更好的开源)?

c - if 语句中使用的 C 函数中对数组类型表达式进行赋值错误

c - 这个c代码有什么问题吗?

assembly - 为什么这个不必要的 MOVAPD 在 gcc 9.1 中复制,在一个小函数中

sse - 使用 SSE/AVX/AVX2 检查 __m128i 的所有字节是否与单个字节匹配

c - 这段 C 代码是如何工作的

linux - 无法将共享库与 -mx32 和 gcc 4.7 或 gcc 4.8 链接

android - 在 OSX 上使用 Make 为 Android 编译

c++ - 如何读取文本并将其放入 OpenCV 中的图像?

x86 - 显示向量寄存器的约定