Оптимизация кода SSE GCC
этот пост тесно связан с другим, который я опубликовал несколько дней назад. На этот раз я написал простой код, который просто добавляет пару массивов элементов, умножает результат на значения в другом массиве и сохраняет его в четвертом массиве, все переменные с плавающей запятой с двойной точностью набраны.
Я сделал две версии этого кода: одну с инструкциями 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 intstructions, если специально не заданы параметры-mno-sse (и другие). Я проверил код сборки, сгенерированный для второго кода, и я заметил, что он использует movsd, addsd и mulsd инструкция. Поэтому он использует инструкции SSE, но только те, которые используют самую низкую часть регистров, если я не ошибаюсь. Этот код сборки, сгенерированный для первого кода C, использовал, как и ожидалось,addp и mulpd инструкции, хотя был сгенерирован довольно большой код сборки.
в любом случае, первый код должен получить лучшую прибыль, насколько я знаю, парадигмы SIMD, так как каждая итерация вычисляется два значения результата. Тем не менее, второй код выполняет что-то вроде 25 процентов быстрее, чем первый. Я также сделал тест с одиночными значениями точности и получаю такой же результат. В чем причина?
2 ответов
векторизация в GCC включена на -O3
. Вот почему at -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 , %eax
cmpl 000, %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 , %eax
cmpl 000, %eax
jne .L3
отметим, что v
перед каждой инструкцией и этой инструкцией используйте 256-битные регистры YMM,четыре итерации исходного цикла выполняются параллельно.
Я хотел бы выразить остынь уже!--5--> и обратите внимание на то, что 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 , %rax
vaddsd b+80000(%rax), %xmm0, %xmm0
vmulsd c+80000(%rax), %xmm0, %xmm0
vmovsd %xmm0, r+80000(%rax)
cmpq $-80000, %rax
jne .L5