OpenMP atomic _mm_add_pd

Я пытаюсь использовать OpenMP для распараллеливания уже векторизованного кода со встроенными функциями, но проблема в том, что я использую один регистр XMM в качестве внешней «переменной», которую я увеличиваю в каждом цикле. Сейчас я использую shared пункт

__m128d xmm0 = _mm_setzero_pd();
__declspec(align(16)) double res[2];

#pragma omp parallel for shared(xmm0)
for (int i = 0; i < len; i++)
{
__m128d xmm7 = ... result of some operations

xmm0 = _mm_add_pd(xmm0, xmm7);
}

_mm_store_pd(res, xmm0);
double final_result = res[0] + res[1];

поскольку atomic операция не поддерживается (в VS2010)

__m128d xmm0 = _mm_setzero_pd();
__declspec(align(16)) double res[2];

#pragma omp parallel for
for (int i = 0; i < len; i++)
{
__m128d xmm7 = ... result of some operations

#pragma omp atomic
xmm0 = _mm_add_pd(xmm0, xmm7);
}

_mm_store_pd(res, xmm0);
double final_result = res[0] + res[1];

Кто-нибудь знает умный обходной путь?


РЕДАКТИРОВАТЬ: я также попробовал это с помощью библиотеки параллельных шаблонов только сейчас:

__declspec(align(16)) double res[2];
combinable<__m128d> xmm0_comb([](){return _mm_setzero_pd();});

parallel_for(0, len, 1, [&xmm0_comb, ...](int i)
{
__m128d xmm7 = ... result of some operations

__m128d& xmm0 = xmm0_comb.local();
xmm0 = _mm_add_pd(xmm0, xmm7);
});

__m128d xmm0 = xmm0_comb.combine([](__m128d a, __m128d b){return _mm_add_pd(a, b);});
_mm_store_pd(res, xmm0);
double final_result = res[0] + res[1];

но результат был разочаровывающим.

1

Решение

Вы решаете проблему неправильно. Вы должны использовать сокращение вместо атомарных операций:

Это лучший подход:

double sum = 0;

#pragma omp parallel for reduction(+:sum)
for (int i = 0; i < len; i++)
{
__m128d xmm7;// = ... result of some operations

//  Collapse to a "double".
_declspec(align(16)) double res[2];
_mm_store_pd(res, xmm7);

//  Add to reduction variable.
sum += res[0] + res[1];
}

double final_result = sum;

Сокращение — это, по сути, операция, которая сворачивает «сводит» все к одной переменной, используя ассоциативную операцию, такую ​​как +,

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

Причиной этого является то, что подходы атомарного / критического секций по своей природе не масштабируемы, поскольку они поддерживают длинную зависимость данных критического пути. Правильный подход к сокращению уменьшает этот критический путь к log(# of threads),

Единственным недостатком, конечно, является то, что он нарушает ассоциативность с плавающей точкой. Если это имеет значение, то вы в основном застряли с последовательным суммированием каждой итерации.

4

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

То, что вы ищете, это сокращение. Вы можете сделать это как сокращение omp, если ваш компилятор поддерживает это (gcc делает), или вы можете свернуть один самостоятельно, суммируя в частный xmm для каждого потока. Ниже приведена простая версия, которая делает оба:

#include <emmintrin.h>
#include <omp.h>
#include <stdio.h>int main(int argc, char **argv) {

const int NTHREADS=8;
const int len=100;

__m128d xmm0[NTHREADS];
__m128d xmmreduction = _mm_setzero_pd();
#pragma omp parallel for num_threads(NTHREADS)
for (int i=0; i<NTHREADS; i++)
xmm0[i]= _mm_setzero_pd();

__attribute((aligned(16))) double res[2];

#pragma omp parallel num_threads(NTHREADS) reduction(+:xmmreduction)
{
int tid = omp_get_thread_num();
#pragma omp for
for (int i = 0; i < len; i++)
{
double d = (double)i;
__m128d xmm7 = _mm_set_pd( d, 2.*d );

xmm0[tid] = _mm_add_pd(xmm0[tid], xmm7);
xmmreduction = _mm_add_pd(xmmreduction, xmm7);
}
}

for (int i=1; i<NTHREADS; i++)
xmm0[0] = _mm_add_pd(xmm0[0], xmm0[i]);

_mm_store_pd(res, xmm0[0]);
double final_result = res[0] + res[1];

printf("Expected result   = %f\n", 3.0*(len-1)*(len)/2);
printf("Calculated result = %lf\n", final_result);

_mm_store_pd(res, xmmreduction);
final_result = res[0] + res[1];

printf("Calculated result (reduction) = %lf\n", final_result);

return 0;
}
2

С большой помощью людей, которые ответили на мой вопрос, я придумал это:

double final_result = 0.0;

#pragma omp parallel reduction(+:final_result)
{
__declspec(align(16)) double r[2];
__m128d xmm0 = _mm_setzero_pd();

#pragma omp for
for (int i = 0; i < len; i++)
{
__m128d xmm7 = ... result of some operations

xmm0 = _mm_add_pd(xmm0, xmm7);
}
_mm_store_pd(r, xmm0);
final_result += r[0] + r[1];
}

Он в основном разделяет развал и сокращение, очень хорошо работает.

Большое спасибо всем, кто мне помог!

2

Я думаю, вы не можете добавить свои собственные встроенные функции в компилятор, и компиляторы MS решили пропустить встроенный ассемблер. Не уверен, что есть простое решение вообще.

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