Вы используете плохой метод для умножения ваших матриц. Алгоритм ijk генерирует много ошибок кэша. Посмотри на свою внутреннюю петлю. Всякий раз, когда ваш индекс k изменяется, вы переходите на новые строки матрицы b
вместо того, чтобы использовать дружественный кешу обход по строке. И это большое количество пропусков кеша снижает вашу производительность и более неприятно для параллельного кода из-за алгоритмов когерентности кеша. Алгоритм ikj (см. Код ниже) намного лучше. Все матрицы проходят через главную строку и не генерируют пропуски в кеше.
Я пытался поэкспериментировать с вашим кодом.
Чтобы иметь постоянную синхронизацию, я делаю 10 циклов умножения матриц, и я делаю это 10 раз, и я держу самое низкое время.
В зависимости от определений можно выбрать ijk или ikj и управлять параллелизмом.
Другое определение выберите параллельную или последовательную версию.
#include <stdio.h>
#include <omp.h>
#include <stdlib.h>
int main() {
double a[400][400], b[400][400], c[400][400] = { { 0.0 } };
int i, j, k, n = 400;
double t1, t2,t;
srand(100); // better be deterministic when benchmarking
for (i = 0; i < n; ++i) {
for (j = 0; j < n; ++j) {
a[i][j] = rand() / (double) RAND_MAX;
b[i][j] = rand() / (double) RAND_MAX;
}
}
t=1E100;
for(int ll=0;ll<10;ll++){
t1 = omp_get_wtime();
for(int mm=0;mm<10;mm++){
#if THREADS>1
#pragma omp parallel for private(i,j,k) num_threads(THREADS)
#endif
#ifdef ijk
for (i=0; i<n; ++i) {
for (j=0; j<n; ++j) {
for (k=0; k<n; ++k) {
c[i][j] += a[i][k] * b[k][j];
}
}
}
#else // ikj matrix multiplication
for (i=0; i<n; ++i) {
for (k=0; k<n; ++k) {
double r=a[i][k];
for (j=0; j<n; ++j) {
c[i][j] += r * b[k][j];
}
}
}
#endif
}
t2 = omp_get_wtime();
if (t>t2-t1) t=t2-t1;
}
printf("%g\n",t);
// to fool these smart optimizers, do something with c
FILE* devnull=fopen("/dev/null","w");
fprintf(devnull,"%g\n",c[0][0]);
return EXIT_SUCCESS;
}
Теперь эксперименты:
Сначала с ijk
am@Mandel$ cc -fopenmp -O3 -march=native -DTHREADS=0 -Dijk omp2.c; ./a.out
0.196313
am@Mandel$ cc -fopenmp -O3 -march=native -DTHREADS=4 -Dijk omp2.c; ./a.out
0.293023
И мы видим, что параллельная версия на 50% медленнее.
Теперь переключаемся на ikj
am@Mandel$ cc -fopenmp -O3 -march=native -DTHREADS=0 -Uijk omp2.c; ./a.out
0.114659
am@Mandel$ cc -fopenmp -O3 -march=native -DTHREADS=4 -Uijk omp2.c; ./a.out
0.06113
Теперь последовательный код в два раза быстрее, а параллельная версия в два раза быстрее последовательного.
Вероятно, с помощью больших матриц вы можете повысить эффективность параллельного кода.