12

这是我的代码:

double res1[NNN];  
#pragma omp parallel for collapse(3) schedule(dynamic) 
for (int i=0; i<NNN; i++)
{
    for (int j=0;j<NNN;j++)
    {
        for (int k=0;k<NNN;k++)
        {
            res1[i] = log(fabs(i*j*k));
        }
    }
}
std::cout<< res1[10] << std::endl;

当我使用collapse(3)它需要大约 50 秒;无需collapse(3)约 6-7 秒。我对这种行为感到非常困惑,因为我本以为“崩溃”会比没有“崩溃”有更好的表现。

我错过了什么吗?


我做了一些实验并使用了不同的配置:

(NNN = 2500 和 24 核)

  1. schedule(STATIC)&& collapse(3)-> ~54 秒
  2. schedule(STATIC)&& collapse(2)-> ~8 秒
  3. schedule(STATIC)&& collapse(1)-> ~8 秒

我也尝试了DYNAMIC日程安排,但这需要大量时间(几分钟)。


在我最初的问题中,我有 4 个 DIM“for-loops”(4D 数组):51x23x51x23。

使用 OpenMP/MPI 最小化运行时间的最佳方法是什么?我总共有大约 300 个 CPU 内核。将我的阵列分布在这些核心上的最佳方式是什么?数组的长度是灵活的(我可以以某种方式将它与 CPU 的数量相匹配)。

有什么建议么?

4

1 回答 1

25

您错过了使用动态调度对 OpenMP 开销有什么影响的概念。

应该使用动态调度来帮助我们解决负载平衡问题,其中每个循环迭代可能需要不同的时间,而静态迭代分布很可能会在不同线程之间造成工作不平衡。工作不平衡会导致 CPU 时间浪费,因为较早完成的线程只是等待其他线程完成。动态调度通过在先到先服务的基础上分配循环块来克服这一点。但这增加了开销,因为 OpenMP 运行时系统必须实现记录哪些迭代被发出,哪些没有迭代,并且必须实现某种类型的同步。此外,每个线程在每次完成其迭代块并去寻找另一个时都必须至少调用一次 OpenMP 运行时。使用静态调度,所有迭代块最初都是预先计算的,然后每个线程在其部分上运行,而不与 OpenMP 运行时环境进行任何交互。

静态调度和动态调度之间最关键的区别是迭代块大小(即每个线程在寻求在迭代空间的另一部分工作之前所做的连续循环迭代的数量)。如果省略,则静态调度的块大小默认为 ,#_of_iterations/#_of_threads而动态调度的默认块大小为1,即每个线程必须向 OpenMP 运行时询问分布式循环的每次迭代

在您的情况下发生的情况是,如果没有collapse(3)外部NNN循环的迭代块,并且每个线程NNN*NNN在询问 OpenMP 运行时进行另一次迭代之前运行迭代(内部循环的)。当您折叠循环时,迭代块的数量增长到NNN*NNN*NNN,即有更多的块,并且每个线程将在每次迭代后向 OpenMP 运行时请求一个块。

当内部循环与最外层一起折叠时,这会带来另一个问题:会发生许多线程会获得具有相同值的迭代i,这会破坏计算,因为不能保证执行顺序并且可能会发生最后一个线程写入res1[i]的不是执行两个内部循环的最后一次迭代的那个。

于 2013-03-01T15:26:08.297 回答