Я изучаю это руководство по OpenMP и Я наткнулся на это упражнение на странице 19. Это алгоритм вычисления числа Пи, который мне нужно распараллелить:

static long num_steps = 100000;
double step;
void main ()
{
  int i;
  double x, pi
  double sum = 0.0;
  step = 1.0 / (double)num_steps;

  for(i = 0; i < num_steps; i++)
  {
     x = (I + 0.5) * step;
     sum = sum + 4.0 / (1.0 + x*x);
  }

  pi = step * sum;
}

Я пока не могу использовать #pragma parallel for. Могу использовать только:

#pragma omp parallel {}
omp_get_thread_num();
omp_set_num_threads(int);
omp_get_num_threads();

Моя реализация выглядит так:

#define NUM_STEPS 800

int main(int argc, char **argv)
{
   int num_steps = NUM_STEPS;
   int i;
  double x;
  double pi;
  double step = 1.0 / (double)num_steps;

  double sum[num_steps];

  for(i = 0; i < num_steps; i++)
  {
      sum[i] = 0;
  }

  omp_set_num_threads(num_steps);
  #pragma omp parallel
  {
    x = (omp_get_thread_num() + 0.5) * step;
    sum[omp_get_thread_num()] += 4.0 / (1.0 + x * x);
  }

  double totalSum = 0;

  for(i = 0; i < num_steps; i++)
  {
    totalSum += sum[i];
  }

  pi = step * totalSum;

  printf("Pi: %.5f", pi);
}

Игнорируя проблему, используя массив сумм (позже он объясняет, что необходимо определить критическую секцию для значения суммы с помощью #pragma omp critical или #pragma omp atomic), указанная выше импелентация работает только для ограниченного числа потоков (800 дюймов). мой случай), где серийный код использует 100000 шагов. Есть ли способ добиться этого с помощью только вышеупомянутых команд OpenMP, или я обязан использовать параллельный #pragma omp для, который еще не упоминался в учебнике?

Большое спасибо за ваше время, я действительно пытаюсь понять концепцию распараллеливания в C с использованием OpenMP.

1
Vector Sigma 11 Ноя 2018 в 04:01

1 ответ

Лучший ответ

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

Самый простой способ - сделать что-то вроде:

int tid = omp_get_thread_num();
int n_threads = omp_get_num_threads();

for (int i = tid; i < num_steps; i += n_threads) {
    // ...
}

Таким образом, работа распределяется по всем потокам независимо от количества потоков.

Если было 3 потока и 9 шагов:

  • Поток 0 будет выполнять шаги 0, 3, 6
  • Поток 1 будет выполнять шаги 1, 4, 7
  • Поток 2 будет выполнять шаги 2, 5, 8

Это работает, но не идеально, если каждый поток обращается к данным из некоторого общего массива. Лучше, если потоки будут обращаться к разделам данных поблизости для целей locality.

В этом случае вы можете разделить количество шагов на количество потоков и дать каждому потоку непрерывный набор задач, например:

int tid = omp_get_thread_num();
int n_threads = omp_get_num_threads();

int steps_per_thread = num_steps / n_threads;
int start = tid * steps_per_thread;
int end = start + steps_per_thread;

for (int i = start; i < end; i++) {
    // ...
}

Теперь 3 потока, выполняющие 9 шагов, выглядят так:

  • Поток 0 выполняет шаги 0, 1, 2
  • Поток 1 выполняет шаги 3, 4, 5
  • Поток 2 выполняет шаги 6, 7, 8

Этот подход наиболее вероятен при использовании #pragma omp for. В большинстве случаев компилятор просто разделяет задачи по количеству потоков и назначает каждому потоку раздел.

Таким образом, учитывая набор из 2 потоков и 100 итераций для цикла, компилятор, скорее всего, даст итерации 0-49 для потока 0 и итерации 50-99 для потока 1.

Обратите внимание, что если количество итераций не делится равномерно на количество потоков, остаток необходимо обрабатывать явно.

3
Increasingly Idiotic 11 Ноя 2018 в 05:53