Как исправить петлю openMP медленнее, чем последовательная версия? - PullRequest
0 голосов
/ 10 февраля 2019

Я пытаюсь реализовать вычисления с массивами для вычисления перманента.

Проблема в том, что цикл, который я написал в C ++ (последовательный), быстрее, чем параллельная версия с openMP.

Это часть кода.

    int t,k,j,i;
    double result = 0;

    #pragma omp parallel for private (t,k,j,i)
    for (int t = 0; t < cols; ++t) {
        double delta = 1;

        #pragma omp parallel for reduction(*: delta)
        for (int k = 0; k < m; ++k) {
            delta *= trasposta[k][t];
        }

        double p = 1;
        for (int j = 0; j < m; j++) {
            double s = 0;

            #pragma omp parallel for reduction(+: s)
            for (int i = 0; i < m; i++) {
                s += trasposta[i][t] * matr[i][j];
            }
            p *= s;
        }
        result += delta * p;
    }

    permanent = result / cols;
    cout << permanent << endl;

Параллельная версия времени выполнения с openMP:

real    0m0,334s
user    0m0,387s
sys     0m0,207s

Последовательная версия времени выполнения (без всех #pragma omp)

real    0m0,100s
user    0m0,095s
sys     0m0,005s

Как я могу решить, чтобы получить лучшие результаты с opeMP?

Редактировать: Я скомпилировал версию openMP с помощью команды:

g++ -fopenmp permanent.cpp -o permanent

Ответы [ 2 ]

0 голосов
/ 13 февраля 2019

Не уверен, он полностью ответит на ваш вопрос, но с вашим кодом много проблем.

Что касается распараллеливания, у вас есть несколько вложенных разделов omp.Благодаря вложенному параллелизму каждый поток создает в каждом разделе max_threads новые потоки.Так что если max_threads = 10 с 3-мя уровнями вложенности, вы получите 1000 потоков !!Без вложенного параллелизма, который кажется вашей ситуацией, внутренние параллельные параллели просто игнорируются.Поэтому удалите их.

Что касается omp, то внутренние сокращения относятся к локальным переменным потока и могут быть подавлены.Но как насчет result+=delta*p.Это сокращение для глобальной переменной и должно обрабатываться как таковое.Настоящий код содержит ошибки.

Возможно, худшим в вашем коде является способ доступа к данным. Все обращения к вашим матрицам осуществляются в кеше недружественным способом.И что еще хуже, он запрещает оптимизацию (например, векторизацию simd) компилятором.Минимум - это транспонировать ваши матрицы (что я и сделал), но вы можете переосмыслить свой алгоритм, чтобы он больше не обращал внимания на кэш.Если вы не знаете об этих проблемах, посмотрите на что такое кеш-код .

Несколько случайных замечаний.Объявления Tijk бесполезны.При присваивании double константой, оно не должно быть int (для результата delta, s).

Не используйте время (1) для измерения времени выполнения.Есть более точные измерения с rdtsc, times (2) или clock (3) (которые я использовал).Я подозреваю, что ваш код будет очень быстрым, когда-то должным образом оптимизирован, и для правильного сравнения вы должны рассмотреть несколько циклов.Отрегулируйте количество петель, как вам нужно.И запустить несколько раз, вы программируете.Вы увидите, что время выполнения не является детерминированным.И удалить выбросы.

Последнее, но не менее важное.Вы не должны никогда не измерять производительность без оптимизации вашего кода .Используйте gcc -O2 или -O3.

#include <time.h>

// transposed versions of matrices. To get efficient transpose code, look
// at /4102494/effektivnaya-programma-transponirovaniya-matritsy-v-keshe
// it is a quick and dirty hack,  
// but you should rethink your algorithms to use properly caches
double Tmatr[m][m];
double Ttrasposta[cols][m];

clock_t start, end;

start=clock(); //tic

#define NLOOPS 100
// to adjust

// run the code several times
for(int l=0; l<NLOOPS; l++)
{ 

double result = 0.0;

#pragma omp parallel for reduction(+:result)
for (int t = 0; t < cols; ++t) {
  double delta = 1.0;

  for (int k = 0; k < m; ++k) {
    delta *= Ttrasposta[t][k];
  }

  double p = 1.0;
  for (int j = 0; j < m; j++) {
    double s = 0.0;
    for (int i = 0; i < m; i++) {
      s += Ttrasposta[t][i] * Tmatr[j][i];
    }
    p *= s;
  }
  result += delta * p;
 }

permanent = result / cols;

 }

end=clock();//toc

cout << permanent << endl;
cout << "Time: " << (double)(end-start) << endl ;

Последнее замечание.Теперь с чистым кодом вы можете поэкспериментировать со своей программой.Я не смог этого сделать, потому что отсутствуют важные данные: например, размер матриц.M = 4 или 100000?То же самое для cols.Это может серьезно изменить состояние параллелизации.Вот почему вы всегда должны предоставлять Минимальный, завершенный, проверяемый пример .

В зависимости от этих значений, может быть, лучше свернуть вашу 'omp параллель для'.

0 голосов
/ 13 февраля 2019

Вещи, которые я предлагаю проверить в вашем коде:

  • переменные t, k, j и i объявляются дважды;
  • операторы приращения циклаиногда префикс, а иногда и постфикс.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...