2016-09-16 4 views
2

Я пытаюсь использовать авто-векторию с g ++ 5.4 (-ftree-vectorize). Я заметил, что версия массива в коде ниже что-то заставляет компилятор пропускать возможность векторизации во внутреннем цикле, что приводит к существенной разнице в производительности по сравнению с версией указателя. Есть ли что-нибудь, что можно сделать, чтобы помочь компилятору в этом случае?Array vs pointer auto-vectorization в gcc

void floydwarshall(float* mat, size_t n) { 
#if USE_POINTER 
    for (int k = 0; k < n; ++k) { 
     for (int i = 0; i < n; ++i) { 
      auto v = mat[i*n + k]; 
      for (int j = 0; j < n; ++j) { 
       auto val = v + mat[k*n+j]; 
       if (mat[i*n + j] > val) { 
        mat[i*n + j] = val; 
       } 
      } 
     } 
    } 
#else // USE_ARRAY 
    typedef float (*array)[n]; 
    array m = reinterpret_cast<array>(mat); 
    for (int k = 0; k < n; ++k) { 
     for (int i = 0; i < n; ++i) { 
      auto v = m[i][k]; 
      for (int j = 0; j < n; ++j) { 
       auto val = v + m[k][j]; 
       if (m[i][j] > val) { 
        m[i][j] = val; 
       } 
      } 
     } 
    } 
#endif 
} 
+1

Лучше избегать ветвей, 'mat [i * n + j] = (mat [i * n + j]> val)? Val: mat [i * n + j];' (безусловная запись) является проще векторизовать. –

+0

Я разделил ваш код на две отдельные функции (чтобы посмотреть на оба сразу) и [поместить их в проводник компилятора Godbolt] (https://godbolt.org/g/i0ByaJ). Они оба авто-векторизации с gcc5.4 '-O3 -march = haswell', используя vcmpltps/vmaskmovps во внутреннем цикле по той причине, о которой указывает Марк. Версия указателя имеет две целые нагрузки из памяти внутри внутреннего цикла, а версия массива имеет внутренний цикл, связанный в памяти ('cmp r11, QWORD PTR [rbp-72]'), поэтому они оба имеют проблемы. Какие целевые параметры вы использовали? Просто базовый SSE2? –

ответ

2

Обе версии делают Vectorize с г ++ 5.4 -O3 -march=haswell, используя vcmpltps/vmaskmovps во внутреннем цикле по той причине, Марк указывает.

Если вы не позволяете компилятору использовать инструкции AVX, это будет труднее. Но я не вижу ни одной версии векторизации вообще, если я просто использую -O3 (так что доступен только SSE2, так как это базовая линия для x86-64). Поэтому ваш исходный вопрос основан на результате, который я не могу воспроизвести.

Изменение if() на тернарный оператор (поэтому код всегда хранится в массиве) позволяет компилятору загружать/MINPS/безоговорочно хранить. Это большой объем трафика памяти, если ваша матрица не вписывается в кеш; возможно, вы можете организовать свои петли по-другому? Или, может быть, нет, так как требуется m[i][k], и я предполагаю, что имеет значение, какой порядок происходит.

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


Вот версия массива, векторизация хорошо, даже только с SSE2. Я добавил код, чтобы сообщить компилятору, что вход выравнивается, а размер кратен 8 (количество поплавков на вектор AVX). Если ваш реальный код не может сделать эти предположения, тогда выньте эту часть. Это облегчает поиск векторизованной части, потому что она не похожа на скалярный код intro/cleanup. (Используя -O2 -ftree-vectorize, этот код полностью не разворачивается, но -O3 делает.)

Я заметил, что без AVX gcc по-прежнему использует невыровненные нагрузки, но выравнивает магазины. Может быть, он не понимает, что размер, кратный 8, должен выровнять m[k][j], если m[i][j] выровнен? Это может быть разница между версией указателя и версией массива.

code on the Godbolt compiler explorer

void floydwarshall_array_unconditional(float* mat, size_t n) { 

    // try to tell gcc that it doesn't need scalar intro/outro code 
    // The vectorized inner loop isn't particularly different without these, but it means less wading through scalar cleanup code (and less bloat if you can use this in your real code). 

    // works with gcc6, doesn't work with gcc5.4 
    mat = (float*)__builtin_assume_aligned(mat, 32); 
    n /= 8; 
    n *= 8;   // code is simpler if matrix size is always a multiple of 8 (floats per AVX vector) 

    typedef float (*array)[n]; 
    array m = reinterpret_cast<array>(mat); 

    for (size_t k = 0; k < n; ++k) { 
     for (size_t i = 0; i < n; ++i) { 
      auto v = m[i][k]; 
      for (size_t j = 0; j < n; ++j) { 
       auto val = v + m[k][j]; 
       m[i][j] = (m[i][j]>val) ? val : m[i][j]; // Marc's suggested change: enables vectorization with unconditional stores. 
      } 
     } 
    } 
} 

gcc5.4 не удается избежать скалярную интро/код очистки вокруг векторизованного части, но gcc6.2 делает. Векторизованная часть в основном одинакова из обеих версий компилятора.

## The inner-most loop (with gcc6.2 -march=haswell -O3) 
.L5: 
    vaddps ymm0, ymm1, YMMWORD PTR [rsi+rax] 
    vminps ymm0, ymm0, YMMWORD PTR [rdx+rax]  #### Note use of minps and unconditional store, enabled by using the ternary operator instead of if(). 
    add  r14, 1 
    vmovaps YMMWORD PTR [rdx+rax], ymm0 
    add  rax, 32 
    cmp  r14, r13 
    jb  .L5 

Следующий цикл снаружи, что делает некоторое целое проверка (используя некоторый SETcc материал) счетчик, и делает vmovss xmm1, DWORD PTR [rax+r10*4] и отдельный vbroadcastss ymm1, xmm1. Предположительно скалярная очистка, в которую он прыгает, не нуждается в широковещательной передаче, и gcc не знает, что было бы дешевле в целом просто использовать VBROADCASTSS в качестве нагрузки, даже если вещательная часть не нужна.