Разница между разделом и задачей openmp

В чем разница в OpenMP между:

#pragma omp parallel sections
{
    #pragma omp section
    {
       fct1();
    }
    #pragma omp section
    {
       fct2();
    }
}

а также :

#pragma omp parallel 
{
    #pragma omp single
    {
       #pragma omp task
       fct1();
       #pragma omp task
       fct2();
    }
}

Не уверен, что второй код правильный ...


person Arkerone    schedule 09.12.2012    source источник
comment
Помимо отсутствия ; в конце обоих операторов, второй код правильный.   -  person Hristo Iliev    schedule 09.12.2012


Ответы (2)


Разница между задачами и разделами заключается в сроках выполнения кода. Разделы заключены в конструкцию sections, и (если не указано условие nowait) потоки не покинут ее, пока все разделы не будут выполнены:

                 [    sections     ]
Thread 0: -------< section 1 >---->*------
Thread 1: -------< section 2      >*------
Thread 2: ------------------------>*------
...                                *
Thread N-1: ---------------------->*------

Здесь N потоки сталкиваются с sections конструкцией с двумя разделами, причем второй занимает больше времени, чем первый. Первые два потока выполняют по одному разделу. Остальные N-2 потоки просто ждут у неявного барьера в конце конструкции секций (показывать здесь как *).

Задачи ставятся в очередь и выполняются, когда это возможно, в так называемых точках планирования задач. При некоторых условиях среде выполнения может быть разрешено перемещать задачу между потоками даже в середине их жизненного цикла. Такие задачи называются несвязанными, и несвязанная задача может начать выполняться в одном потоке, а затем в какой-то момент планирования она может быть перенесена средой выполнения в другой поток.

Тем не менее, задачи и разделы во многом схожи. Например, следующие два фрагмента кода дают практически одинаковый результат:

// sections
...
#pragma omp sections
{
   #pragma omp section
   foo();
   #pragma omp section
   bar();
}
...

// tasks
...
#pragma omp single nowait
{
   #pragma omp task
   foo();
   #pragma omp task
   bar();
}
#pragma omp taskwait
...

taskwait работает очень похоже на barrier, но для задач - он гарантирует, что текущий поток выполнения будет приостановлен до тех пор, пока все задачи в очереди не будут выполнены. Это точка планирования, то есть она позволяет потокам обрабатывать задачи. Конструкция single нужна для того, чтобы задачи создавались только одним потоком. Если бы не было single конструкции, каждая задача создавалась бы num_threads раз, что может быть не тем, что нужно. Предложение nowait в конструкции single указывает другим потокам не ждать, пока конструкция single будет выполнена (т. Е. Удаляет неявный барьер в конце конструкции single). Так они сразу попадают в taskwait и приступают к обработке задач.

taskwait - явная точка планирования, показанная здесь для ясности. Существуют также неявные точки планирования, в первую очередь внутри барьерной синхронизации, независимо от того, явные они или неявные. Следовательно, приведенный выше код можно также записать просто как:

// tasks
...
#pragma omp single
{
   #pragma omp task
   foo();
   #pragma omp task
   bar();
}
...

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

               +--+-->[ task queue ]--+
               |  |                   |
               |  |       +-----------+
               |  |       |
Thread 0: --< single >-|  v  |-----
Thread 1: -------->|< foo() >|-----
Thread 2: -------->|< bar() >|-----

Здесь в | ... | показано действие точки планирования (либо директива taskwait, либо неявный барьер). Обычно поток 1 и 2 приостанавливает свои действия в этот момент и начинает обработку задач из очереди. Как только все задачи будут обработаны, потоки возобновят свой нормальный поток выполнения. Обратите внимание, что потоки 1 и 2 могут достичь точки планирования до того, как поток 0 выйдет из конструкции single, поэтому нет необходимости выравнивать левые | (это показано на диаграмме выше).

Также может случиться так, что поток 1 сможет завершить обработку задачи foo() и запросить другую еще до того, как другие потоки смогут запросить задачи. Таким образом, и foo(), и bar() могут выполняться одним и тем же потоком:

               +--+-->[ task queue ]--+
               |  |                   |
               |  |      +------------+
               |  |      |
Thread 0: --< single >-| v             |---
Thread 1: --------->|< foo() >< bar() >|---
Thread 2: --------------------->|      |---

Также возможно, что выделенный поток может выполнить вторую задачу, если поток 2 приходит слишком поздно:

               +--+-->[ task queue ]--+
               |  |                   |
               |  |      +------------+
               |  |      |
Thread 0: --< single >-| v < bar() >|---
Thread 1: --------->|< foo() >      |---
Thread 2: ----------------->|       |---

В некоторых случаях компилятор или среда выполнения OpenMP могут даже полностью обойти очередь задач и выполнить задачи последовательно:

Thread 0: --< single: foo(); bar() >*---
Thread 1: ------------------------->*---
Thread 2: ------------------------->*---

Если в коде региона нет точек планирования задач, среда выполнения OpenMP может запускать задачи, когда сочтет нужным. Например, возможно, что все задачи будут отложены до тех пор, пока не будет достигнут барьер в конце области parallel.

person Hristo Iliev    schedule 09.12.2012
comment
+1, @ Arkerone да, это хорошее объяснение, вы также должны проголосовать за :) - person dreamcrash; 10.12.2012
comment
Есть ли большая разница при использовании трех последовательных одиночных игр против секций? - person dreamcrash; 14.02.2013
comment
@HristoIliev Есть ли у вас источник задачи, которая создается много раз, когда прагма задачи не находится в одной прагме? Я не вижу ничего, что предполагало бы это в документации IBM OpenMP. - person Chris; 26.02.2013
comment
@Chris, спецификация OpenMP 3.1 §2.7.1: когда поток встречает конструкцию задачи, задача генерируется из кода для связанного структурированного блока. Если не существует single/master`, или конструкция совместной работы, или условные выражения, каждый поток выполняет один и тот же код, и, следовательно, все потоки сталкиваются с директивой task. - person Hristo Iliev; 27.02.2013
comment
Я считаю, что нет необходимости выполнять задачи nowait и #pragma mop wait. Потоки начинают делать свою работу, как только задачи созданы. поправьте меня, если я ошибаюсь. - person towi_parallelism; 23.04.2013
comment
@HristoIliev: Другие потоки N-2 просто ждут у неявного барьера в конце конструкции секций. Если другие потоки не назначены ни одному из разделов в этом кластере параллельных разделов, нужно ли им еще ждать? - person Joe C; 14.09.2015
comment
@JoeC, sections - это конструкция для совместной работы, что означает, что все потоки в группе, связанной с данной параллельной областью, должны встретиться с ней, чтобы конструкция была успешной. Если нежелательно, чтобы незанятые потоки ожидали на неявном барьере, применяется предложение nowait, которое удаляет неявный барьер. - person Hristo Iliev; 14.09.2015
comment
@HristoIliev: Понятно. Это означает, что потокам, не относящимся к параллельной области, не нужно ждать на этом барьере. - person Joe C; 14.09.2015
comment
@JoeC, это правильно. Но обратите внимание, что смешивание OpenMP с другими парадигмами потоковой передачи, например pthreads или std::thread, хотя отлично работает, например, с среда выполнения GCC OpenMP не стандартизирована и может привести к непереносимости кода. - person Hristo Iliev; 14.09.2015
comment
@HristoIliev Можем ли мы, таким образом, ответить на первоначальный вопрос OP о разнице между задачами и разделами, что в основном это проблема производительности? Задачи обеспечивают большую гибкость для планирования времени выполнения (если, конечно, мы не учитываем зависимость новых задач) - person Manuel Selva; 04.04.2016
comment
@ManuelSelva, я бы воздержался от утверждения, что основное различие между двумя конструкциями - это производительность, поскольку она сильно зависит от среды выполнения OpenMP. - person Hristo Iliev; 05.04.2016
comment
Я тестирую последовательность fib как по задаче, так и по разделам, но не уверен, верна она или нет. Это слишком длинный комментарий, поэтому я помещаю его под вашим ответом. Можно ли сказать, что задача намного мудрее разделов при распределении вычислительных ресурсов? - person Yiling Liu; 17.07.2020
comment
@YilingLiu Я не уверен, что вы понимаете под словом «мудрее». - person Hristo Iliev; 19.07.2020
comment
Отличный ответ, но самое важное различие между Задачами и Разделами, возможно, стоит подчеркнуть (даже мысль не относится к приведенному выше коду): Разделы статичны, то есть количество разделов устанавливается при написании кода, тогда как Задачи могут быть создается в любое время и в любом количестве под управлением логики программы. - person Laci; 04.07.2021

Я не эксперт в OpenMP, но пытался протестировать последовательность fib на своей машине, используя как task, так и sections

разделы

int fib(int n)
{
    int i, j;
    if (n < 2)
        return n;
    else
    {
#pragma omp parallel sections       
{
#pragma omp section             
{
                i = fib(n - 1);
            }
#pragma omp section             
{
                j = fib(n - 2);
            }
        }
        printf("Current int %d is on thread %d \n", i + j, omp_get_thread_num());
        return i + j;
    }
}

int main()
{
    int n = 10;

#pragma omp parallel shared(n)  {
#pragma omp single      {
            printf("%d\n", omp_get_num_threads());
            printf("fib(%d) = %d\n", n, fib(n));
        }
    }
}

задача

#include <stdio.h>
#include <omp.h>
int fib(int n)
{
  int i, j;
  if (n<2)
    return n;
  else
    {
       #pragma omp task shared(i) firstprivate(n)
       i=fib(n-1);

       #pragma omp task shared(j) firstprivate(n)
       j=fib(n-2);

       #pragma omp taskwait
    printf("Current int %d is on thread %d \n", i + j, omp_get_thread_num());
       return i+j;
    }
}

int main()
{
  int n = 10;

  #pragma omp parallel shared(n)
  {
    #pragma omp single
    {
    printf("%d\n", omp_get_num_threads());
        printf ("fib(%d) = %d\n", n, fib(n));
    }
  }
}

Результат по разделам:

12
Current int 1 is on thread 0
Current int 2 is on thread 0
Current int 1 is on thread 0
Current int 3 is on thread 0
Current int 1 is on thread 0
Current int 2 is on thread 0
Current int 5 is on thread 0
Current int 1 is on thread 0
Current int 2 is on thread 0
Current int 1 is on thread 0
Current int 3 is on thread 0
Current int 8 is on thread 0
Current int 1 is on thread 0
Current int 2 is on thread 0
Current int 1 is on thread 0
Current int 3 is on thread 0
Current int 1 is on thread 0
Current int 2 is on thread 0
Current int 5 is on thread 0
Current int 13 is on thread 0
Current int 1 is on thread 0
Current int 2 is on thread 0
Current int 1 is on thread 0
Current int 3 is on thread 0
Current int 1 is on thread 0
Current int 2 is on thread 0
Current int 5 is on thread 0
Current int 1 is on thread 0
Current int 2 is on thread 0
Current int 1 is on thread 0
Current int 3 is on thread 0
Current int 8 is on thread 0
Current int 21 is on thread 0
Current int 1 is on thread 0
Current int 2 is on thread 0
Current int 1 is on thread 0
Current int 3 is on thread 0
Current int 1 is on thread 0
Current int 2 is on thread 0
Current int 5 is on thread 0
Current int 1 is on thread 0
Current int 2 is on thread 0
Current int 1 is on thread 0
Current int 3 is on thread 0
Current int 8 is on thread 0
Current int 1 is on thread 0
Current int 2 is on thread 0
Current int 1 is on thread 0
Current int 3 is on thread 0
Current int 1 is on thread 0
Current int 2 is on thread 0
Current int 5 is on thread 0
Current int 13 is on thread 0
Current int 34 is on thread 0
Current int 1 is on thread 0
Current int 2 is on thread 0
Current int 1 is on thread 0
Current int 3 is on thread 0
Current int 1 is on thread 0
Current int 2 is on thread 0
Current int 5 is on thread 0
Current int 1 is on thread 0
Current int 2 is on thread 0
Current int 1 is on thread 0
Current int 3 is on thread 0
Current int 8 is on thread 0
Current int 1 is on thread 0
Current int 2 is on thread 0
Current int 1 is on thread 0
Current int 3 is on thread 0
Current int 1 is on thread 0
Current int 2 is on thread 0
Current int 5 is on thread 0
Current int 13 is on thread 0
Current int 1 is on thread 0
Current int 2 is on thread 0
Current int 1 is on thread 0
Current int 3 is on thread 0
Current int 1 is on thread 0
Current int 2 is on thread 0
Current int 5 is on thread 0
Current int 1 is on thread 0
Current int 2 is on thread 0
Current int 1 is on thread 0
Current int 3 is on thread 0
Current int 8 is on thread 0
Current int 21 is on thread 0
Current int 55 is on thread 4
fib(10) = 55

Результат по задаче:

12
Current int 1 is on thread 3
Current int 2 is on thread 3
Current int 1 is on thread 8
Current int 2 is on thread 8
Current int 1 is on thread 8
Current int 1 is on thread 4
Current int 1 is on thread 11
Current int 1 is on thread 11
Current int 2 is on thread 11
Current int 3 is on thread 11
Current int 1 is on thread 11
Current int 2 is on thread 11
Current int 1 is on thread 11
Current int 1 is on thread 11
Current int 2 is on thread 11
Current int 3 is on thread 11
Current int 1 is on thread 11
Current int 2 is on thread 11
Current int 1 is on thread 11
Current int 1 is on thread 11
Current int 2 is on thread 11
Current int 3 is on thread 11
Current int 5 is on thread 11
Current int 8 is on thread 11
Current int 1 is on thread 8
Current int 2 is on thread 8
Current int 3 is on thread 8
Current int 5 is on thread 8
Current int 13 is on thread 8
Current int 1 is on thread 7
Current int 2 is on thread 7
Current int 1 is on thread 7
Current int 1 is on thread 7
Current int 1 is on thread 0
Current int 1 is on thread 0
Current int 2 is on thread 0
Current int 3 is on thread 0
Current int 1 is on thread 1
Current int 1 is on thread 6
Current int 2 is on thread 6
Current int 1 is on thread 9
Current int 2 is on thread 9
Current int 1 is on thread 2
Current int 2 is on thread 7
Current int 3 is on thread 7
Current int 5 is on thread 7
Current int 2 is on thread 5
Current int 5 is on thread 5
Current int 1 is on thread 5
Current int 2 is on thread 5
Current int 1 is on thread 5
Current int 1 is on thread 5
Current int 2 is on thread 5
Current int 3 is on thread 5
Current int 1 is on thread 5
Current int 2 is on thread 5
Current int 1 is on thread 5
Current int 1 is on thread 5
Current int 2 is on thread 5
Current int 3 is on thread 5
Current int 5 is on thread 5
Current int 1 is on thread 5
Current int 2 is on thread 5
Current int 1 is on thread 11
Current int 2 is on thread 11
Current int 1 is on thread 8
Current int 2 is on thread 8
Current int 5 is on thread 8
Current int 3 is on thread 1
Current int 8 is on thread 1
Current int 21 is on thread 1
Current int 1 is on thread 10
Current int 3 is on thread 10
Current int 8 is on thread 0
Current int 1 is on thread 4
Current int 3 is on thread 4
Current int 1 is on thread 9
Current int 3 is on thread 9
Current int 8 is on thread 9
Current int 3 is on thread 2
Current int 5 is on thread 3
Current int 13 is on thread 3
Current int 5 is on thread 6
Current int 13 is on thread 7
Current int 8 is on thread 10
Current int 21 is on thread 10
Current int 34 is on thread 3
Current int 55 is on thread 1
fib(10) = 55

Кажется, что задача намного мудрее разделов при распределении вычислительных ресурсов.

-----------------------------РЕДАКТИРОВАТЬ-------------------- ---------

Для людей, которые ищут ответы на этот вопрос, просмотрите комментарий под этим сообщением.

person Yiling Liu    schedule 17.07.2020
comment
Два примера кода не эквивалентны. Тот, у которого есть разделы, использует вложенный параллелизм, то есть создает новую параллельную область при каждом рекурсивном вызове. Вложенный параллелизм отключен по умолчанию, поэтому все, кроме верхнего уровня рекурсии, выполняется командами из одного потока, поэтому вы видите так много идентификаторов потоков, равных 0. Даже если был включен вложенный параллелизм, вы можете получить тысячи потоков. , что будет действительно неэффективно. - person Hristo Iliev; 19.07.2020
comment
@Hristo Iliev Так можем ли мы вычислить Фибоначчи, используя sections? Я имею в виду, включить параллелизм при использовании sections - person Yiling Liu; 19.07.2020
comment
Только в очень ограниченной степени. Разделы не предназначены для решения рекурсивных задач. Они предназначены для решения случая независимых блоков при линейном выполнении вашей программы. - person Hristo Iliev; 19.07.2020
comment
@Hristo Iliev Понятно - person Yiling Liu; 19.07.2020