В чем преимущества использования vaddss вместо addss в добавлении скалярной матрицы?

Я реализовал ядро ​​сложения скалярных матриц.

#include <stdio.h>
#include <time.h>
//#include <x86intrin.h>

//loops and iterations:
#define N 128
#define M N
#define NUM_LOOP 1000000


float   __attribute__(( aligned(32))) A[N][M],
        __attribute__(( aligned(32))) B[N][M],
        __attribute__(( aligned(32))) C[N][M];

int main()
{
int w=0, i, j;
struct timespec tStart, tEnd;//used to record the processiing time
double tTotal , tBest=10000;//minimum of toltal time will asign to the best time
do{
    clock_gettime(CLOCK_MONOTONIC,&tStart);

    for( i=0;i<N;i++){
        for(j=0;j<M;j++){
            C[i][j]= A[i][j] + B[i][j];
        }
    }

    clock_gettime(CLOCK_MONOTONIC,&tEnd);
    tTotal = (tEnd.tv_sec - tStart.tv_sec);
    tTotal += (tEnd.tv_nsec - tStart.tv_nsec) / 1000000000.0;
    if(tTotal<tBest)
        tBest=tTotal;
    } while(w++ < NUM_LOOP);

printf(" The best time: %lf sec in %d repetition for %dX%d matrix\n",tBest,w, N, M);
return 0;
}

В этом случае я скомпилировал программу с другим флагом компилятора, а вывод сборки внутреннего цикла следующий:

gcc -O2 msse4.2: лучшее время: 0,000024 сек в повторении 406490 для матрицы 128X128

movss   xmm1, DWORD PTR A[rcx+rax]
addss   xmm1, DWORD PTR B[rcx+rax]
movss   DWORD PTR C[rcx+rax], xmm1

gcc -O2 -mavx: лучшее время: 0,000009 секунд в повторении 1000001 для матрицы 128X128

vmovss  xmm1, DWORD PTR A[rcx+rax]
vaddss  xmm1, xmm1, DWORD PTR B[rcx+rax]
vmovss  DWORD PTR C[rcx+rax], xmm1

Версия AVX gcc -O2 -mavx:

__m256 vec256;
for(i=0;i<N;i++){   
    for(j=0;j<M;j+=8){
        vec256 = _mm256_add_ps( _mm256_load_ps(&A[i+1][j]) ,  _mm256_load_ps(&B[i+1][j]));
        _mm256_store_ps(&C[i+1][j], vec256);
            }
        }

Версия SSE gcc -O2 -sse4.2::

__m128 vec128;
for(i=0;i<N;i++){   
    for(j=0;j<M;j+=4){
    vec128= _mm_add_ps( _mm_load_ps(&A[i][j]) ,  _mm_load_ps(&B[i][j]));
    _mm_store_ps(&C[i][j], vec128);
            }
        }

В скалярной программе ускорение -mavx над msse4.2 равно 2.7x. Я знаю, что avx улучшил ISA эффективно, и это может быть из-за этих улучшений. Но когда я реализовал программу в intrinsics для avx и SSE, ускорение является фактором 3x. Вопрос в том, что сканер AVX в 2,7 раза быстрее, чем SSE, когда я векторизовал его, скорость выше 3x (размер матрицы составляет 128x128 для этого вопроса). Имеет ли смысл при использовании AVX и SSE в режиме скалярного режима, ускорение 2.7x. но векторный метод должен быть лучше, потому что я обрабатываю восемь элементов в AVX по сравнению с четырьмя элементами в SSE. Все программы имеют менее 4,5% промахов в кэше, о которых сообщается perf stat.

используя gcc -O2, linux mint, skylake

ОБНОВЛЕНИЕ: Вкратце, Scalar-AVX в 2,7 раза быстрее, чем Scalar-SSE, но AVX-256 только в 3 раза быстрее, чем SSE-128, в то время как он векторизован. Я думаю, это может быть из-за конвейерной обработки. в скалярном я имею 3 vec-ALU, которые могут не использоваться в векторизованном режиме. Я мог бы сравнивать яблоки с апельсинами вместо яблок с яблоками, и это может означать, что я не могу понять причину.

Ответ 1

Проблема, которую вы наблюдаете, объясняется здесь. В системах Skylake, если верхняя половина регистра AVX загрязнена, тогда существует ложная зависимость для операций с бездействующим кодированием SSE в верхней половине регистра AVX. В вашем случае кажется, что в вашей версии glibc 2.23 есть ошибка. На моей системе Skylake с Ubuntu 16.10 и glibc 2.24 у меня нет проблемы. Вы можете использовать

__asm__ __volatile__ ( "vzeroupper" : : : ); 

чтобы очистить верхнюю половину регистра AVX. Я не думаю, что вы можете использовать встроенный, такой как _mm256_zeroupper, чтобы исправить это, потому что GCC скажет это SSE-код и не распознает внутреннее. Параметры -mvzeroupper не будут работать либо потому, что GCC снова думает, что это SSE-код, и не будет генерировать команду vzeroupper.

BTW, это ошибка Microsoft, что аппаратное обеспечение имеет эту проблему.


Update:

Другие люди, по-видимому, сталкиваются с этой проблемой на Skylake. Это наблюдалось после printf, memset и clock_gettime.

Если вы хотите сравнить 128-битные операции с 256-битными операциями, можете рассмотреть возможность использования -mprefer-avx128 -mavx (что особенно полезно для AMD). Но тогда вы будете сравнивать AVX256 и AVX128, а не AVX256 против SSE. AVX128 и SSE используют 128-битные операции, но их реализация различна. Если вы ориентируетесь, вы должны указать, какой из них вы использовали.