Оптимизация кода с использованием встроенных функций Intel SSE для векторизации

Я впервые работаю со встроенными функциями SSE. Я пытаюсь преобразовать простой фрагмент кода в более быструю версию, используя встроенную технологию Intel SSE (до SSE4.2). Кажется, я столкнулся с рядом ошибок.

Скалярная версия кода: (простое матричное умножение)

     void mm(int n, double *A, double *B, double *C)
     {
        int i,j,k;
        double tmp;

        for(i = 0; i < n; i++)
            for(j = 0; j < n; j++) {
                    tmp = 0.0;
                    for(k = 0; k < n; k++)
                            tmp += A[n*i+k] *
                                   B[n*k+j];
                    C[n*i+j] = tmp;

              }
            }

Это моя версия: я включил #include <ia32intrin.h>

      void mm_sse(int n, double *A, double *B, double *C)
      {
        int i,j,k;
        double tmp;
        __m128d a_i, b_i, c_i;

        for(i = 0; i < n; i++)
            for(j = 0; j < n; j++) {
                    tmp = 0.0;
                    for(k = 0; k < n; k+=4)
                            a_i = __mm_load_ps(&A[n*i+k]);
                            b_i = __mm_load_ps(&B[n*k+j]);
                            c_i = __mm_load_ps(&C[n*i+j]);

                            __m128d tmp1 = __mm_mul_ps(a_i,b_i);
                            __m128d tmp2 = __mm_hadd_ps(tmp1,tmp1);
                            __m128d tmp3 = __mm_add_ps(tmp2,tmp3);
                            __mm_store_ps(&C[n*i+j], tmp3);

            }
         }

Где я ошибаюсь в этом? Я получаю несколько ошибок, как это:

mm_vec.c(84): ошибка: значение типа "int" не может быть присвоено объекту типа "__m128d" a_i = __mm_load_ps(&A[n*i+k]);

Вот как я компилирую: icc -O2 mm_vec.c -o vec

Может кто-нибудь, пожалуйста, помогите мне точно преобразовать этот код. Спасибо!

ОБНОВИТЬ:

В соответствии с вашими предложениями, я сделал следующие изменения:

       void mm_sse(int n, float *A, float *B, float *C)
       {
         int i,j,k;
         float tmp;
         __m128 a_i, b_i, c_i;

         for(i = 0; i < n; i++)
            for(j = 0; j < n; j++) {
                    tmp = 0.0;
                    for(k = 0; k < n; k+=4)
                            a_i = _mm_load_ps(&A[n*i+k]);
                            b_i = _mm_load_ps(&B[n*k+j]);
                            c_i = _mm_load_ps(&C[n*i+j]);

                            __m128 tmp1 = _mm_mul_ps(a_i,b_i);
                            __m128 tmp2 = _mm_hadd_ps(tmp1,tmp1);
                            __m128 tmp3 = _mm_add_ps(tmp2,tmp3);
                            _mm_store_ps(&C[n*i+j], tmp3);


            }
        }

Но теперь я, кажется, получаю ошибку сегментации. Я знаю это, возможно, потому, что я неправильно обращаюсь к индексам массива для массива A, B, C. Я очень новичок в этом и не уверен, как поступить с этим.

Пожалуйста, помогите мне определить правильный подход к обработке этого кода.


person PGOnTheGo    schedule 08.06.2012    source источник
comment
__m128d tmp3 = __mm_add_ps(tmp2,tmp3); — полная ерунда: вы используете переменную в том виде, в каком она объявлена, то есть неинициализирована. См. раздел Получить сумму значений, хранящихся в __m256d, с помощью SSE/AVX для получения правильных и эффективных горизонтальных сумм. (Но на самом деле вы не хотите делать это во внутреннем цикле. Примените SIMD ко второму циклу, например, для параллельного вычисления скалярных произведений строк*столбцов. Кроме того, _ps упаковано -single; не то, что вы хотите для double*.   -  person Peter Cordes    schedule 30.01.2019


Ответы (2)


Ошибка, которую вы видите, связана с тем, что у вас слишком много символов подчеркивания в именах функций, например:

__mm_mul_ps

должно быть:

_mm_mul_ps // Just one underscore up front

поэтому компилятор C предполагает, что они возвращают int, поскольку он не видел объявления.

Помимо этого, есть и другие проблемы - вы, кажется, смешиваете вызовы с двойными и одиночными вариантами с плавающей запятой одной и той же инструкции.

Например, у вас есть:

__m128d a_i, b_i, c_i;

но вы звоните:

__mm_load_ps(&A[n*i+k]);

который возвращает __m128, а не __m128d - вы хотели позвонить:

_mm_load_pd

вместо. Аналогично для других инструкций, если вы хотите, чтобы они работали с парами двойников.


Если вы видите необъяснимые ошибки сегментации и в коде SSE, я склонен предположить, что у вас есть проблемы с выравниванием памяти - указатели, передаваемые внутренним функциям SSE (в основном1), должны быть выровнены по 16 байтам. . Вы можете проверить это с помощью простого утверждения в своем коде или проверить его в отладчике (вы ожидаете, что последняя цифра указатель равен 0, если он правильно выровнен).

Если он не выровнен правильно, вам нужно убедиться, что это так. Для объектов, не выделенных с помощью new/malloc(), вы можете сделать это с помощью расширения компилятора (например, с gcc):

float a[16] __attribute__ ((aligned (16)));

При условии, что ваша версия gcc имеет достаточно большое максимальное выравнивание, чтобы поддерживать это и несколько других предостережений о выравнивании стека. Для динамически выделяемого хранилища вы захотите использовать расширение для конкретной платформы, например. posix_memalign для выделения подходящего хранилища:

float *a=NULL;
posix_memalign(&a, __alignof__(__m128), sizeof(float)*16);

(Я думаю, что могут быть более удобные переносимые способы сделать это с С++ 11, но я пока не уверен в этом на 100%).

1 Есть несколько инструкций, которые позволяют выполнять невыровненные загрузки и сохранения, но они ужасно медленные по сравнению с выровненными загрузками, и их стоит по возможности избегать.

person Flexo    schedule 08.06.2012
comment
Я работаю с icc, а не с gcc. Как вы думаете, правильно ли обращаться с этим следующим образом: a_i = _mm_load_ps(&A[n*i+k]); Примеры, которые я вижу в другом месте (даже в документации Intel Intrinsic), содержат очень простые примеры. массивы A, B, C были выделены с помощью malloc. - person PGOnTheGo; 08.06.2012
comment
@Hello_PG Сама загрузка не является неправильной. Однако вам не нужно загружать c_i. По большей части ICC имеет те же расширения, что и gcc - я думаю, что это относится к выравниванию, я больше знаком с GCC, чем с ICC, поэтому я уточнил его с помощью этого и связался с документами, которые я знал, как найти. malloc не гарантирует подходящее выравнивание на всех платформах, поэтому, вероятно, потребуется posix_memalign. Утверждение, которое я предложил, провалилось? - person Flexo; 08.06.2012
comment
Когда я пытаюсь выделить память для A следующим образом: A = (float*)_aligned_malloc(dimensiondimensionsizeof(float),16); Я получаю ошибку компиляции: неопределенная ссылка на `aligned_malloc' с icc. вот как я компилирую: icc -O2 mm_vec.c -o vec2 - person PGOnTheGo; 08.06.2012
comment
@Hello_PG - это либо aligned_mallloc (примечание: без подчеркивания), хотя, вероятно, вы все равно хотите использовать posix_memalign, а не aligned malloc. (Вы, вероятно, захотите включить предупреждения компилятора и отметить, когда он говорит о неявных объявлениях функций). - person Flexo; 08.06.2012

Вы должны убедиться, что ваши загрузки и хранилища всегда обращаются к адресам, выровненным по 16 байтам. В качестве альтернативы, если вы не можете гарантировать это по какой-либо причине, используйте _mm_loadu_ps/_mm_storeu_ps вместо _mm_load_ps/_mm_store_ps - это будет менее эффективно, но не приведет к сбою на неправильно выровненных адресах.

person Paul R    schedule 08.06.2012