Как намекнуть OpenMP Stride?

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

я использую

Оптимизирующий компилятор Microsoft (R) C / C ++ версии 17.00.60315.1 для x64

С OpenMP:

информация C5002: цикл не векторизован по причине ‘502’

Без OpenMP:

информация C5001: петля векторизована

ВС страница векторизации говорит, что эта ошибка происходит, когда:

Индукционная переменная ступенчато отличается от простого +1

Могу ли я заставить его шагнуть с первого шага?

Петля

#pragma omp parallel for
for (int j = 0; j < H*W; j++)//A,B,C,D,IN are __restricted
{
float Gs = D[j]-B[j];
float Gc = A[j]-C[j];
in[j]=atan2f(Gs,Gc);
}

Лучшее усилие(?)

#pragma omp parallel
{// This seems to vectorize, but it still requires quite a lot of boiler code
int middle = H*W/2;
#pragma omp sections nowait
{
#pragma omp section
for (int j = 0; j < middle; j++)
{
float Gs = D[j]-B[j];
float Gc = A[j]-C[j];
in[j]=atan2f(Gs,Gc);
}
#pragma omp section
for (int j = middle; j < H*W; j++)
{
float Gs = D[j]-B[j];
float Gc = A[j]-C[j];
in[j]=atan2f(Gs,Gc);
}
}
}

3

Решение

Я рекомендую вам сделать векторизацию вручную. Одна из причин заключается в том, что автоматическая векторизация, по-видимому, плохо обрабатывает переносимые зависимости цикла (развертывание цикла).

Чтобы избежать раздувания кода и загадочных особенностей, я использую векторный класс Agner Fog. По моему опыту, это так же быстро, как использование встроенных функций, и оно автоматически использует преимущества SSE2-AVX2 (AVX2 протестирован на эмуляторе Intel) в зависимости от того, как вы компилируете. Я написал код GEMM с использованием векторного класса, который работает на SSE2 до AVX2, и когда я работаю в системе с AVX, мой код уже работает быстрее, чем Eigen, который использует только SSE. Вот ваша функция с векторным классом (я не пробовал развернуть цикл).

#include "omp.h"#include "math.h"
#include "vectorclass.h"#include "vectormath.h"
void loop(const int H, const int W, const int outer_stride, float *A, float *B, float *C, float *D, float* in) {
#pragma omp parallel for
for (int j = 0; j < H*W; j+=8)//A,B,C,D,IN are __restricted, W*H must be a multiple of 8
{
Vec8f Gs = Vec8f().load(&D[j]) - Vec8f().load(&B[j]);
Vec8f Gc = Vec8f().load(&A[j]) - Vec8f().load(&C[j]);
Vec8f invec = atan(Gs, Gc);
invec.store(&in[j]);
}

}

Делая векторизацию самостоятельно, вы должны быть осторожны с границами массива. В функции выше HW должен быть кратным 8. Существует несколько решений для этого, но самое простое и эффективное решение — сделать массивы (A, B, C, D, in) немного больше (максимум 7 поплавков), если необходимо, чтобы быть кратным 8. Однако другое решение заключается в использовании следующего кода, который не требует WH, чтобы быть кратным 8, но это не так красиво.

#define ROUND_DOWN(x, s) ((x) & ~((s)-1))
void loop_fix(const int H, const int W, const int outer_stride, float *A, float *B, float *C, float *D, float* in) {
#pragma omp parallel for
for (int j = 0; j < ROUND_DOWN(H*W,8); j+=8)//A,B,C,D,IN are __restricted
{
Vec8f Gs = Vec8f().load(&D[j]) - Vec8f().load(&B[j]);
Vec8f Gc = Vec8f().load(&A[j]) - Vec8f().load(&C[j]);
Vec8f invec = atan(Gs, Gc);
invec.store(&in[j]);
}
for(int j=ROUND_DOWN(H*W,8); j<H*W; j++) {
float Gs = D[j]-B[j];
float Gc = A[j]-C[j];
in[j]=atan2f(Gs,Gc);
}

}

Одной из проблем, связанных с выполнением векторизации самостоятельно, является поиск математической библиотеки SIMD (например, для atan2f). Векторный класс поддерживает 3 варианта. Non-SIMD, LIBM от AMD и SVML от Intel (в коде выше я использовал опцию non-SIMD).
SIMD математические библиотеки для SSE и AVX

Некоторые последние комментарии, которые вы можете рассмотреть. Visual Studio имеет автоматическое распараллеливание (отключено по умолчанию), а также автоматическое векторизация (включено по умолчанию, по крайней мере, в режиме выпуска). Вы можете попробовать это вместо OpenMP, чтобы уменьшить раздувание кода.
http://msdn.microsoft.com/en-us/library/hh872235.aspx

Кроме того, у Microsoft есть библиотека параллельных шаблонов. На это стоит обратить внимание, поскольку поддержка Microsoft OpenMP ограничена. Это почти так же просто, как использовать OpenMP. Возможно, что одна из этих опций лучше работает с автовекторизацией (хотя я сомневаюсь в этом). Как я уже сказал, я бы сделал векторизацию вручную с векторным классом.

2

Другие решения

Вы можете попробовать развернуть цикл вместо sections:

#pragma omp parallel for
for (int j = 0; j < H*W; j += outer_stride)//A,B,C,D,IN are __restricted
{
for (int ii = 0; ii < outer_stride; ii++) {
float Gs = D[j+ii]-B[j+ii];
float Gc = A[j+ii]-C[j+ii];
in[j+ii] = atan2f(Gs,Gc);
}
}

где outer_stride является подходящим кратным вашей SIMD линии. Кроме того, вы можете найти это ответ полезно.

1

По вопросам рекламы [email protected]