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