GCC SSE оптимизация кода - PullRequest
       23

GCC SSE оптимизация кода

14 голосов
/ 27 октября 2011

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

Я сделал две версии этого кода: одну с инструкциями SSE, используя вызовы, а другую без них, а затем скомпилировал их с уровнем оптимизации gcc и -O0.Я пишу их ниже:

// SSE VERSION

#define N 10000
#define NTIMES 100000
#include <time.h>
#include <stdio.h>
#include <xmmintrin.h>
#include <pmmintrin.h>

double a[N] __attribute__((aligned(16)));
double b[N] __attribute__((aligned(16)));
double c[N] __attribute__((aligned(16)));
double r[N] __attribute__((aligned(16)));

int main(void){
  int i, times;
  for( times = 0; times < NTIMES; times++ ){
     for( i = 0; i <N; i+= 2){ 
        __m128d mm_a = _mm_load_pd( &a[i] );  
        _mm_prefetch( &a[i+4], _MM_HINT_T0 );
        __m128d mm_b = _mm_load_pd( &b[i] );  
        _mm_prefetch( &b[i+4] , _MM_HINT_T0 );
        __m128d mm_c = _mm_load_pd( &c[i] );
        _mm_prefetch( &c[i+4] , _MM_HINT_T0 );
        __m128d mm_r;
        mm_r = _mm_add_pd( mm_a, mm_b );
        mm_a = _mm_mul_pd( mm_r , mm_c );
        _mm_store_pd( &r[i], mm_a );
      }   
   }
 }

//NO SSE VERSION
//same definitions as before
int main(void){
  int i, times;
   for( times = 0; times < NTIMES; times++ ){
     for( i = 0; i < N; i++ ){
      r[i] = (a[i]+b[i])*c[i];
    }   
  }
}

При компиляции с помощью -O0, gcc использует регистры XMM / MMX и инструкции SSE, если специально не указаны параметры -mno-sse (и другие).Я проверил код ассемблера, сгенерированный для второго кода, и заметил, что он использует инструкции movsd , addd и mulsd .Так что он использует инструкции SSE, но только те, которые используют самую низкую часть регистров, если я не ошибаюсь.Код сборки, сгенерированный для первого кода C, использовал, как и ожидалось, инструкции addp и mulpd , хотя был сгенерирован довольно большой код сборки.

В любом случае, первый код должен, насколько мне известно, получать большую прибыль от парадигмы SIMD, поскольку на каждой итерации вычисляются два результирующих значения.Тем не менее, второй код выполняет что-то вроде 25 процентов быстрее, чем первый.Я также сделал тест с одинарной точностью и получил аналогичные результаты.В чем причина?

Ответы [ 2 ]

15 голосов
/ 10 ноября 2011

Векторизация в GCC включена на -O3. Вот почему на -O0 вы видите только обычные скалярные инструкции SSE2 (movsd, addsd и т. Д.). Используя GCC 4.6.1 и ваш второй пример:

#define N 10000
#define NTIMES 100000

double a[N] __attribute__ ((aligned (16)));
double b[N] __attribute__ ((aligned (16)));
double c[N] __attribute__ ((aligned (16)));
double r[N] __attribute__ ((aligned (16)));

int
main (void)
{
  int i, times;
  for (times = 0; times < NTIMES; times++)
    {
      for (i = 0; i < N; ++i)
        r[i] = (a[i] + b[i]) * c[i];
    }

  return 0;
}

и компиляция с gcc -S -O3 -msse2 sse.c производит для внутреннего цикла следующие инструкции, что довольно неплохо:

.L3:
    movapd  a(%eax), %xmm0
    addpd   b(%eax), %xmm0
    mulpd   c(%eax), %xmm0
    movapd  %xmm0, r(%eax)
    addl    $16, %eax
    cmpl    $80000, %eax
    jne .L3

Как видите, с включенной векторизацией GCC выдает код для выполнения двух итераций цикла параллельно. Однако его можно улучшить - этот код использует младшие 128 битов регистров SSE, но он может использовать полные 256-битные регистры YMM, включив AVX-кодирование инструкций SSE (если доступно на машине). Итак, компиляция той же программы с gcc -S -O3 -msse2 -mavx sse.c дает для внутреннего цикла:

.L3:
    vmovapd a(%eax), %ymm0
    vaddpd  b(%eax), %ymm0, %ymm0
    vmulpd  c(%eax), %ymm0, %ymm0
    vmovapd %ymm0, r(%eax)
    addl    $32, %eax
    cmpl    $80000, %eax
    jne .L3

Обратите внимание, что v перед каждой инструкцией и что инструкции используют 256-битные регистры YMM, четыре итерации исходного цикла выполняются параллельно.

2 голосов
/ 21 апреля 2016

Я хотел бы расширить ответ Чилла и обратить ваше внимание на тот факт, что GCC, по-видимому, не в состоянии сделать то же самое умное использование инструкций AVX при повторении в обратном направлении.

Просто замените внутренний цикл в примере кода chill на:

for (i = N-1; i >= 0; --i)
    r[i] = (a[i] + b[i]) * c[i];

GCC (4.8.4) с опциями -S -O3 -mavx производит:

.L5:
    vmovsd  a+79992(%rax), %xmm0
    subq    $8, %rax
    vaddsd  b+80000(%rax), %xmm0, %xmm0
    vmulsd  c+80000(%rax), %xmm0, %xmm0
    vmovsd  %xmm0, r+80000(%rax)
    cmpq    $-80000, %rax
    jne     .L5
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...