部分和任务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(); } }
我不确定第二个代码是否正确
任务和部分之间的区别在于代码执行的时间范围内。 部分包含在sections
结构中,并且(除非指定了nowait
子句)线程将不会离开它直到所有部分都被执行完毕:
[ sections ] Thread 0: -------< section 1 >---->*------ Thread 1: -------< section 2 >*------ Thread 2: ------------------------>*------ ... * Thread N-1: ---------------------->*------
这里有N
线程会遇到一个由两部分组成的部分,第二部分比第一部分花费更多的时间。 前两个线程分别执行一个部分。 其他的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
但任务 – 它确保当前的执行stream程将暂停,直到所有排队的任务已经执行。 这是一个调度点,即它允许线程处理任务。 single
构造是必需的,因此任务将仅由一个线程创build。 如果没有single
构造,每个任务都会创buildnum_threads
次,这可能不是我们想要的。 single
构造中的nowait
子句指示其他线程不要等到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
挂起了他们正在做的事情,并开始处理队列中的任务。 一旦所有任务都被处理完毕,线程恢复正常的执行stream程。 请注意,线程1
和2
可能在线程0
退出single
构造之前到达调度点,因此左边的|
不需要alignment(这在上面的图表中表示)。
可能还会发生线程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
区域末尾的障碍。