Оптимизация SSE SIMD для цикла - PullRequest
6 голосов
/ 27 мая 2010

У меня есть код в цикле

for(int i = 0; i < n; i++)
{
  u[i] = c * u[i] + s * b[i];
}

Итак, u и b - векторы одинаковой длины, а c и s - скаляры. Является ли этот код хорошим кандидатом для векторизации для использования с SSE для ускорения?

UPDATE

Я изучил векторизацию (оказывается, это не так сложно, если вы используете встроенные функции) и реализовал мой цикл в SSE. Однако при установке флага SSE2 в компиляторе VC ++ я получаю примерно ту же производительность, что и с моим собственным кодом SSE. Компилятор Intel, с другой стороны, был намного быстрее, чем мой код SSE или компилятор VC ++.

Вот код, который я написал для справки

double *u = (double*) _aligned_malloc(n * sizeof(double), 16);
for(int i = 0; i < n; i++)
{
   u[i] = 0;
}

int j = 0;
__m128d *uSSE = (__m128d*) u;
__m128d cStore = _mm_set1_pd(c);
__m128d sStore = _mm_set1_pd(s);
for (j = 0; j <= i - 2; j+=2)
{
  __m128d uStore = _mm_set_pd(u[j+1], u[j]);

  __m128d cu = _mm_mul_pd(cStore, uStore);
  __m128d so = _mm_mul_pd(sStore, omegaStore);

  uSSE[j/2] = _mm_add_pd(cu, so);
}
for(; j <= i; ++j)
{
  u[j] = c * u[j] + s * omegaCache[j];
}

Ответы [ 5 ]

5 голосов
/ 27 мая 2010

Да, это отличный кандидат на векторизацию. Но прежде чем сделать это, убедитесь, что вы профилировали свой код , чтобы убедиться, что это действительно стоит оптимизировать. Тем не менее, векторизация будет выглядеть примерно так:

int i;
for(i = 0; i < n - 3; i += 4)
{
  load elements u[i,i+1,i+2,i+3]
  load elements b[i,i+1,i+2,i+3]
  vector multiply u * c
  vector multiply s * b
  add partial results
  store back to u[i,i+1,i+2,i+3]
}

// Finish up the uneven edge cases (or skip if you know n is a multiple of 4)
for( ; i < n; i++)
  u[i] = c * u[i] + s * b[i];

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

1 голос
/ 20 декабря 2011

Да, это отличный кандидат на векторизацию, при условии, что нет перекрытия массивов U и B. Но код связан с доступом к памяти (загрузка / сохранение). Векторизация помогает сократить количество циклов в цикле, но инструкции останавливаются из-за отсутствия кэша в массиве U и B. Компилятор Intel C / C ++ генерирует следующий код с флагами по умолчанию для процессора Xeon x5500. Компилятор развертывает цикл на 8 и использует инструкции SIMD ADD (addpd) и MULTIPLY (mulpd) с использованием регистров xmm [0-16] SIMD. В каждом цикле процессор может выдавать 2 SIMD-инструкции, получая 4-стороннюю скалярную ILP, при условии, что у вас есть готовые данные в регистрах.

Здесь U, B, C и S имеют двойную точность (8 байт).

    ..B1.14:                        # Preds ..B1.12 ..B1.10
    movaps    %xmm1, %xmm3                                  #5.1
    unpcklpd  %xmm3, %xmm3                                  #5.1
    movaps    %xmm0, %xmm2                                  #6.12
    unpcklpd  %xmm2, %xmm2                                  #6.12
      # LOE rax rcx rbx rbp rsi rdi r8 r12 r13 r14 r15 xmm0 xmm1 xmm2 xmm3
    ..B1.15:     # Preds ..B1.15 ..B1.14
    movsd     (%rsi,%rcx,8), %xmm4                          #6.21
    movhpd    8(%rsi,%rcx,8), %xmm4                         #6.21
    mulpd     %xmm2, %xmm4                                  #6.21
    movaps    (%rdi,%rcx,8), %xmm5                          #6.12
    mulpd     %xmm3, %xmm5                                  #6.12
    addpd     %xmm4, %xmm5                                  #6.21
    movaps    16(%rdi,%rcx,8), %xmm7                        #6.12
    movaps    32(%rdi,%rcx,8), %xmm9                        #6.12
    movaps    48(%rdi,%rcx,8), %xmm11                       #6.12
    movaps    %xmm5, (%rdi,%rcx,8)                          #6.3
    mulpd     %xmm3, %xmm7                                  #6.12
    mulpd     %xmm3, %xmm9                                  #6.12
    mulpd     %xmm3, %xmm11                                 #6.12
    movsd     16(%rsi,%rcx,8), %xmm6                        #6.21
    movhpd    24(%rsi,%rcx,8), %xmm6                        #6.21
    mulpd     %xmm2, %xmm6                                  #6.21
    addpd     %xmm6, %xmm7                                  #6.21
    movaps    %xmm7, 16(%rdi,%rcx,8)                        #6.3
    movsd     32(%rsi,%rcx,8), %xmm8                        #6.21
    movhpd    40(%rsi,%rcx,8), %xmm8                        #6.21
    mulpd     %xmm2, %xmm8                                  #6.21
    addpd     %xmm8, %xmm9                                  #6.21
    movaps    %xmm9, 32(%rdi,%rcx,8)                        #6.3
    movsd     48(%rsi,%rcx,8), %xmm10                       #6.21
    movhpd    56(%rsi,%rcx,8), %xmm10                       #6.21
    mulpd     %xmm2, %xmm10                                 #6.21
    addpd     %xmm10, %xmm11                                #6.21
    movaps    %xmm11, 48(%rdi,%rcx,8)                       #6.3
    addq      $8, %rcx                                      #5.1
    cmpq      %r8, %rcx                                     #5.1
    jl        ..B1.15       # Prob 99%                      #5.1
1 голос
/ 30 июня 2010

_mm_set_pd не векторизовано. Если понимать буквально, он читает два двойных с использованием скалярных операций, затем объединяет два двойных скалярных и копирует их в регистр SSE. Вместо этого используйте _mm_load_pd.

1 голос
/ 27 мая 2010

вероятно, да, но вы должны помочь компилятору с некоторыми подсказками. __restrict__, помещенный в указатели, говорит компилятору, что между двумя указателями нет псевдонима. если вы знаете выравнивание векторов, сообщите об этом компилятору (Visual C ++ может иметь некоторые возможности).

Я сам не знаком с Visual C ++, но слышал, что он не годится для векторизации. Попробуйте использовать вместо этого компилятор Intel. Intel позволяет довольно тонко контролировать сгенерированную сборку: http://www.intel.com/software/products/compilers/docs/clin/main_cls/cref_cls/common/cppref_pragma_vector.htm

0 голосов
/ 27 мая 2010

это зависит от того, как вы поместили u и b в память. если оба блока памяти находятся далеко друг от друга, в этом сценарии SSE не сильно увеличится.

предлагается, чтобы массивы u и b были AOE (массив структуры) вместо SOA (структура массива), потому что вы можете загрузить их в регистр в одной инструкции.

...