这是我的代码:
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 核)
schedule(STATIC)
&& collapse(3)
-> ~54 秒 schedule(STATIC)
&& collapse(2)
-> ~8 秒 schedule(STATIC)
&& collapse(1)
-> ~8 秒 我也试过
DYNAMIC
计划,但需要大量时间(几分钟)。在我最初的问题中,我有 4 个 DIM“for-loops”(4D 数组):51x23x51x23。
使用 OpenMP/MPI 最小化运行时间的最佳方法是什么?我总共有大约 300 个 CPU 内核。将我的阵列分布在这些核心上的最佳方法是什么?数组的长度是灵活的(我可以通过某种方式让它适应 CPU 的数量)。
有什么建议?
最佳答案
您缺少使用动态调度对 OpenMP 开销有何影响的概念。
应该使用动态调度来帮助我们解决负载平衡问题,其中每个循环迭代可能需要不同的时间,而静态迭代分布很可能会在不同线程之间造成工作不平衡。工作不平衡会导致 CPU 时间浪费,因为较早完成的线程只是等待其他线程完成。
动态调度通过以先到先得的方式分发循环块来克服这个问题。但这会增加开销,因为 OpenMP 运行时系统必须实现记录哪些迭代被发出,哪些没有并且必须实现某种类型的同步。此外,每个线程在每次完成其迭代块并寻找另一个时都必须至少调用一次 OpenMP 运行时。使用静态调度,所有迭代块最初都是预先计算的,然后每个线程运行其部分,而无需与 OpenMP 运行时环境进行任何交互。
静态和动态调度之间最重要的区别是迭代块大小(即每个线程在寻求在迭代空间的另一部分工作之前所做的连续循环迭代次数)。如果省略,静态调度的块大小默认为 #_of_iterations/#_of_threads
而动态调度的默认块大小是 1
,即每个线程必须针对分布式循环的每次迭代询问 OpenMP 运行时。
在你的情况下会发生什么是没有 collapse(3)
你有 NNN
外循环的迭代块和每个线程运行 NNN*NNN
在要求 OpenMP 运行时进行另一次迭代之前进行(内部循环的)迭代。当您折叠循环时,迭代块的数量增加到 NNN*NNN*NNN
,即有更多的块,并且每个线程将在每次迭代后向 OpenMP 运行时询问一个块。
当内循环与最外循环折叠时,这会带来另一个问题:许多线程将获得具有相同值 i
的迭代。 ,这会破坏计算,因为无法保证执行顺序,并且可能会发生写入 res1[i]
的最后一个线程。不是执行两个内部循环的最后一次迭代的那个。
关于performance - 带有 "collapse()"的用于嵌套 for 循环的 OpenMP 在没有时性能更差,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15160570/