OpenMP распараллеливание умножения матрицы на тройной цикл for (проблема производительности)
Я пишу программу для умножения матрицы с OpenMP, которая для удобства кэша реализует умножение строк X строк x строк a x B(транспонирование) вместо классических строк X столбцов a x B, для лучшей эффективности кэша. При этом я столкнулся с интересным фактом, который для меня нелогичен: если в этом коде я распараллеливаю внешний цикл, программа работает медленнее, чем если бы я поместил директивы OpenMP в самый внутренний цикл, на моем компьютере время составляет 10.9 против 8.1 секунд.
//A and B are double* allocated with malloc, Nu is the lenght of the matrixes
//which are square
//#pragma omp parallel for
for (i=0; i<Nu; i++){
for (j=0; j<Nu; j++){
*(C+(i*Nu+j)) = 0.;
#pragma omp parallel for
for(k=0;k<Nu ;k++){
*(C+(i*Nu+j))+=*(A+(i*Nu+k)) * *(B+(j*Nu+k));//C(i,j)=sum(over k) A(i,k)*B(k,j)
}
}
}
2 ответов
попробуйте ударить результат реже. Это наводит делить cacheline и предотвращает деятельность от бежать параллельно. Вместо этого использование локальной переменной позволит выполнять большинство записей в кэше L1 каждого ядра.
кроме того, использование restrict
может помочь. В противном случае компилятор не может гарантировать, что пишет C
ничего не меняется A
и B
.
попробуй:
for (i=0; i<Nu; i++){
const double* const Arow = A + i*Nu;
double* const Crow = C + i*Nu;
#pragma omp parallel for
for (j=0; j<Nu; j++){
const double* const Bcol = B + j*Nu;
double sum = 0.0;
for(k=0;k<Nu ;k++){
sum += Arow[k] * Bcol[k]; //C(i,j)=sum(over k) A(i,k)*B(k,j)
}
Crow[j] = sum;
}
}
кроме того, я думаю, что Elalfer прав о необходимости сокращения, если вы распараллелить внутренний цикл.
вы, вероятно, есть некоторые зависимости в данных, когда вы распараллелить внешний цикл и компилятор не может понять это, и добавляет дополнительные замки.
скорее всего, он решает, что разные итерации внешнего цикла могут писать в одно и то же (C+(i*Nu+j))
и он добавляет блокировки доступа, чтобы защитить его.
компилятор, вероятно, может выяснить, что нет зависимости, если вы будете выполнять 2-ю петлю. Но выяснение того, что зависимостей нет распараллеливание внешнего цикла не так тривиально для компилятора.
обновление
некоторые показатели измерения.
привет еще раз. Это выглядит как 1000 double *
и +
недостаточно, чтобы покрыть стоимость синхронизации потоков.
Я сделал несколько небольших тестов, и простое векторное скалярное умножение не эффективно с openmp, если количество элементов не меньше ~10 ' 000. В принципе, больше выбора, больше производительность вы получите от использования openmp.
таким образом, распараллеливание самого внутреннего цикла вам придется разделить задачу между различными потоками и собрать данные обратно 1'000'000 раз.
PS. Попробуйте Intel ICC, это своего рода бесплатно использовать для студентов и проектов с открытым исходным кодом. Я помню, что использовал openmp для меньших массивов элементов 10'000.
обновление 2: пример сокращения
double sum = 0.0;
int k=0;
double *al = A+i*Nu;
double *bl = A+j*Nu;
#pragma omp parallel for shared(al, bl) reduction(+:sum)
for(k=0;k<Nu ;k++){
sum +=al[k] * bl[k]; //C(i,j)=sum(over k) A(i,k)*B(k,j)
}
C[i*Nu+j] = sum;