Подтвердить что ты не робот

Разница между разделом и задачей 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();
    }
}

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

4b9b3361

Ответ 1

Разница между задачами и разделами заключается в том временном кадре, в котором будет выполняться код. Разделы заключены в конструкцию 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, поэтому левая | не обязательно должна быть выровнена (это представлено на диаграмме выше).

Также может случиться так, что thread 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.