почему "+ =" дает мне неожиданный результат в SSE instrinsic - PullRequest
2 голосов
/ 13 июня 2019

Существует два способа реализации накопления в 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

1 Ответ

5 голосов
/ 13 июня 2019

В GNU C __m128i определяется как вектор 64-битных целых чисел с чем-то вроде

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

Использование собственного векторного синтаксиса GNU C (оператор +) позволяет добавлять каждый элемент с размером 64-битного элемента. т.е. _mm_add_epi64.

В вашем случае вынос из верхней части одного 32-битного элемента добавил дополнительный к 32-битному элементу над ним, потому что размер 64-битного элемента распространяет перенос между парами 32-битных элементов. (Добавление отрицательного значения в ненулевое назначение приводит к выполнению.)


Встроенный API-интерфейс Intel не определяет оператор + для __m128 / __m128d / __m128i. Например, ваш код не будет компилироваться в MSVC.

То есть, поведение, которое вы получаете, зависит только от деталей реализации встроенных типов в заголовках GCC. Это полезно для векторов с плавающей запятой, где существует очевидный размер элемента, но для целочисленных векторов вы захотите определить свой собственный, если у вас нет 64-битных целых чисел.


Если вы хотите использовать v1 += v2;, вы можете определить свои собственные собственные типы векторов GNU C, например,

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 наиболее полезны, когда вы хотите, чтобы компилятор испускал эффективный код для деления на константу времени компиляции или что-то в этом роде. например digit = v1 % 10; и v1 /= 10; с 16-разрядными целыми числами без знака будут компилироваться в pmulhuw и сдвиг вправо. Но они также удобны для читабельного кода.


Существует несколько библиотек-оболочек C ++, которые переносят перегрузки операторов и имеют такие типы, как Vec4i (4x без знака int) / Vec4u (4x без знака int) / Vec16c (16x со знаком char), чтобы дать вам тип система для различных типов целочисленных векторов, так что вы знаете, что получаете от v1 += v2; или v1 >>= 2; (сдвиг вправо - один из случаев, когда подпись имеет значение.)

например. VCL Agner Fog (лицензия GPL) или DirectXMath (лицензия MIT).

...