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;