OpenMP вложение не выключается - PullRequest
2 голосов
/ 08 января 2020

Я пытаюсь управлять вложенными параллельными областями с помощью OpenMP (4.5, через G CC 7.2.0), и у меня возникают некоторые проблемы с отключением вложенности.

Пример программы:

#include <stdio.h>
#include <omp.h>

void foobar() {
  int tid = omp_get_thread_num();
  #pragma omp parallel for
  for (int i = 0; i < 4; i++) {
    int otid = omp_get_thread_num();
    printf("%d | %d\n", tid, otid);
  }
}

int main(void) {
  omp_set_nested(0);
  #pragma omp parallel
  {
    foobar();
  }
  printf("\n");
  foobar();
  return 0;
}

То, что я ожидаю, что произойдет здесь, - это то, что параллельный регион и непараллельный вызов foobar () будут выплевывать 4 строки, что-то на

// parallel region foobar()
0 | 0
1 | 1
2 | 2
3 | 3
// serial region foobar()
0 | 0
0 | 1
0 | 2
0 | 3

Поскольку я не допускаю вложенного параллелизма. Тем не менее, я получаю 16 строк в параллельной области с правильным TID, но OTID всегда равен 0 (т. Е. Каждый поток порождает 4 своих собственных, и на этом выполняется весь l oop), и я получаю 4 строки снаружи ( т.е. параллель for порождает 4 потока, как я и ожидал)

Мне кажется, что я упускаю что-то очень очевидное здесь, кто-нибудь может пролить свет на меня? Разве отключение вложения не должно превращать параллельный omp в обычный omp и распределять работу соответственно?

1 Ответ

3 голосов
/ 08 января 2020

Ваша проблема связана с ложным предположением, что директива omp for будет интерпретирована и соответствующая работа распределена между потоками независимо от того, какая область parallel активна. К сожалению, в вашем коде omp for связан только с регионом parallel, который объявлен в функции foobar(). Поэтому, когда этот регион активирован (то есть, поскольку вы отключили вложенный параллелизм, когда foobar() не вызывается из другого parallel региона), ваш l oop будет распределен среди вновь создаваемых потоков. Но если это не так, поскольку foobar() вызывается из другого региона parallel, то omp for игнорируется, а l oop не распределяется между вызывающими потоками. Таким образом, каждый из них выполняет целое l oop, что приводит к репликации printf(), которую вы видите.

Возможное решение будет выглядеть примерно так:

#include <stdio.h>
#include <omp.h>

void bar(int tid) {
  #pragma omp for
  for (int i = 0; i < 4; i++) {
    int otid = omp_get_thread_num();
    printf("%d | %d\n", tid, otid);
  }
}

void foobar() {
  int tid = omp_get_thread_num();
  int in_parallel = omp_in_parallel();
  if (!in_parallel) {
    #pragma omp parallel
    bar(tid);
  }
  else {
    bar(tid);
  }
}

int main() {
  #pragma omp parallel
  foobar();
  printf("\n");
  foobar();
  return 0;
}

Я не нахожу это решение полностью удовлетворяющим, но сейчас я не вижу лучшего. Возможно, позже я получу некоторое просветление ...

РЕДАКТИРОВАТЬ: ну, у меня была другая идея: сделать это наоборот и форсировать вложенный параллелизм, используя только один активный поток всякий раз, когда функция была вызвана из фактического parallel региона:

#include <stdio.h>
#include <omp.h>

void foobar() {
  int tid = omp_get_thread_num();
  omp_set_nested(1);
  #pragma omp single
  #pragma omp parallel for
  for (int i = 0; i < 4; i++) {
    int otid = omp_get_thread_num();
    printf("%d | %d\n", tid, otid);
  }
}

int main() {
  #pragma omp parallel
  foobar();
  printf("\n");
  foobar();
  return 0;
}

И на этот раз код выглядит намного лучше без дублирования и выдает (например):

$ OMP_NUM_THREADS=4 ./nested
3 | 2
3 | 3
3 | 1
3 | 0

0 | 3
0 | 1
0 | 0
0 | 2
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...