OpenMP动态与引导式调度

时间:2017-03-23 08:27:31

标签: c++ multithreading parallel-processing openmp scheduling

我正在研究OpenMP的日程安排,特别是不同的类型。我理解每种类型的一般行为,但澄清对于何时在dynamicguided日程安排之间进行选择会有所帮助。

Intel's docs描述dynamic日程安排:

  

使用内部工作队列来提供一个块大小的循环块   迭代到每个线程。线程完成后,它会检索   从工作队列顶部开始的下一个循环迭代块。通过   默认情况下,块大小为1.使用此调度时要小心   因为涉及额外的开销而打字。

它还描述了guided日程安排:

  

与动态调度类似,但块大小开始大而且   减少以更好地处理迭代之间的负载不平衡。该   可选的chunk参数指定它们使用的最小大小块。通过   默认情况下,块大小约为loop_count / number_of_threads。

由于guided调度在运行时动态地减少了块大小,为什么我会使用dynamic调度?

我已经研究过这个问题和found this table from Dartmouth

enter image description here

guided列为high开销,而dynamic则有中等开销。

这最初是有道理的,但经过进一步调查,我read an Intel article就这个话题。从上一个表中,我理论上guided调度将花费更长的时间,因为在运行时分析和调整块大小(即使正确使用)。但是,在英特尔的文章中它指出:

  

引导时间表最适合小块大小作为其限制;这个   给予最大的灵活性。目前尚不清楚为什么他们会变得更糟   较大的块大小,但是当限制为大时,它们可能需要很长时间   块大小。

为什么块大小与guided相关的时间超过dynamic?缺乏灵活性是有道理的。通过将块大小锁定得太高而导致性能下降。但是,我不会将其描述为"开销",锁定问题会破坏以前的理论。

最后,它在文章中说明了:

  

动态排程提供最大的灵活性,但占据最大的灵活性   计划错误时性能上升。

dynamic调度比static更优化是有意义的,但为什么它比guided更优化?这只是我质疑的开销吗?

somewhat related SO post解释了与调度类型相关的NUMA。它与这个问题无关,因为所需的组织已经被“先到先得”和#34;这些调度类型的行为。

dynamic时间安排可能会合并,从而导致性能提升,但同样的假设应适用于guided

以下是英特尔文章中不同块大小的每种调度类型的时序,以供参考。它只是来自一个程序的录音,一些规则适用于每个程序和机器(特别是有了调度),但它应该提供一般趋势。

enter image description here

编辑(我的问题的核心):

  • 影响guided日程安排的运行时间的因素是什么?具体例子?在某些情况下,为什么它比dynamic慢?
  • 我何时支持guided而不是dynamic,反之亦然?
  • 解释一下后,上述来源是否支持您的解释?它们完全矛盾吗?

1 个答案:

答案 0 :(得分:23)

  

什么会影响指导调度的运行时间?

需要考虑三种效果:

1。负载平衡

动态/引导调度的重点是在每个循环迭代不包含相同工作量的情况下改进工作分配。从根本上说:

  • schedule(dynamic, 1)提供最佳负载平衡
  • dynamic, k将始终具有相同或更好的负载平衡,而不是guided, k

标准规定每个块的大小比例与未分配的迭代次数除以团队中的线程数, 减少到k

GCC OpenMP implemntation从字面上理解,忽略比例。例如,对于4个线程k=1,它将32次迭代为8, 6, 5, 4, 3, 2, 1, 1, 1, 1。现在恕我直言这是非常愚蠢的:如果前1 / n次迭代包含超过1 / n的工作,则会导致负载不平衡。

  

具体例子?在某些情况下,为什么它比动态慢?

好的,让我们看看一个简单的例子,其中内部工作随着循环迭代而减少:

#include <omp.h>

void work(long ww) {
    volatile long sum = 0;
    for (long w = 0; w < ww; w++) sum += w;
}

int main() {
    const long max = 32, factor = 10000000l;
    #pragma omp parallel for schedule(guided, 1)
    for (int i = 0; i < max; i++) {
         work((max - i) * factor);
    }
}

执行看起来像 1

Example execution timeline

正如您所看到的,guided在这里确实很糟糕。对于不同类型的工作分配,guided会做得更好。也可以不同地实施指导。 clang(IIRC源于英特尔)的实现是much more sophisticated。我真的不明白海湾合作委员会的天真实施背后的想法。在我看来,如果你将1/n工作交给第一个线程,它有效地破坏了动态加载消除的目的。

2。开销

现在,由于访问共享状态,每个动态块都会对性能产生一些影响。 guided的开销每块将略高于dynamic,因为还有更多的计算要做。但是,guided, k的动态总块数将少于dynamic, k

开销还取决于实施,例如是否使用原子或锁来保护共享状态。

3。缓存和NUMA效应

让我们说在循环迭代中写入整数向量。如果每个第二次迭代要由不同的线程执行,则向量的每个第二个元素将由不同的核心写入。这真的很糟糕,因为通过这样做,他们竞争包含相邻元素的缓存行(虚假共享)。如果您的小块大小和/或块大小不能很好地与高速缓存对齐,那么在边缘处会出现性能不佳的问题。大块这就是为什么你通常喜欢大的漂亮(2^n)块大小。 guided平均可以为您提供更大的块大小,但不能2^n(或k*m)。

This answer(您已经引用过),详细讨论了NUMA方面动态/引导式调度的缺点,但这也适用于局部性/缓存。

不要猜,测量

考虑到预测细节的各种因素和难度,我只建议使用您的特定编译器在特定系统上,在特定配置中测量您的特定应用程序。不幸的是,没有完美的性能可移植性。我个人认为,对于guided来说尤其如此。

  

我什么时候会支持引导动态,反之亦然?

如果您对开销/每次迭代工作有特定的了解,我会说dynamic, k通过选择一个好的k为您提供最稳定的结果。特别是,你不太依赖于实现的聪明程度。

另一方面,guided可能是一个很好的初步猜测,具有合理的开销/负载平衡比,至少对于一个聪明的实现。如果您知道以后的迭代时间较短,请特别注意guided

请记住,还有schedule(auto),它可以完全控制编译器/运行时,还有schedule(runtime),它允许您在运行时选择调度策略。

  

一旦解释过,上面的来源是否支持您的解释?他们完全矛盾吗?

取一些来源,包括这个anser,用一粒盐。您发布的图表和我的时间线图片都不是科学上准确的数字。结果存在巨大差异,并且没有误差条,它们可能只是在这些极少数据点的地方。此图表结合了我提到的多种效果,但没有透露Work代码。

[来自英特尔文档]

  

默认情况下,块大小约为loop_count / number_of_threads。

这与我的观察结果相矛盾,即icc更好地处理我的小例子。

1:使用GCC 6.3.1,Score-P / Vampir进行可视化,两个交替的工作函数进行着色。