我遇到了OMP线程问题,因为并行部分中的线程执行的时间非常不同。 我在Linux集群上运行。代码是纯OpenMP代码;例如,没有混合使用的MPI代码。它是用C ++编写的。 我正在使用gcc 4.5.3,因此使用OpenMP 3.0。我正在使用编译优化级别2;即-O2。 我首先给出代码,然后是从它生成的数据。 由于我想循环遍历地图的键,我首先将键复制到矢量vec_keys中,然后我在vec_keys的元素上运行并行for循环。 有一个与OMP并行化的for循环。必须处理800,000个“节点”或条目。并行for循环中有3个“命令”。
根据键获取地图迭代器。见行:node = vec_keys.at(itime);
使用步骤1中的迭代器,获取一个指向C ++对象的指针,该对象将在其上调用一个方法。见行:p_nim = p_dmb-> getModel(node);
在步骤2中对从地图检索到的对象调用方法。参见语句:
isStateUpdate = p_nim-> computeNextState(day,iteration,
fsmId,p_dmb,tid,node,
p_np,p_ep,p_cp,p_fg,newStateNp,
outStream);
请注意,在步骤2中,将检索映射条目,但不会将其写入。 在步骤3中,地图的内容正在变化,但是通过间接变化。也就是说,地图键不会改变。值(在映射条目中)是指向在堆上实例化的基本数组的指针。因此,通过不更改值指针,我可以更改基本数组的内容。关键是我正在使用map,每个键在OMP for循环中被调用一次,并且没有竞争条件或不一致的内存。我已多次运行1,2,4和8个线程,输出始终正确。上述步骤1和2中的操作对于每个映射键是相同的;只有第3步可以有所不同。 代码是:
#pragma omp parallel num_threads(numSpecOmpThreads) \
private(itime,node,index,modelId,isStateUpdate,tid, \
b1time, \
e1time) \
shared(numkeys,vec_keys,p_graph,p_cp,ifsm,p_np,p_dmb, \
p_ep,p_fg,newStateNp,day,iteration,fsmId, \
fakeMpiRankOpenMp,cout,outStream, \
startWtime,endWtime,counter, \
sinnertime,einnertime, \
dt1time, \
dt2time, \
dt3time, \
countChange) \
default(none)
{
// Any variable in here is private.
tid=omp_get_thread_num();
NodeInteractModel02IF *p_nim=0;
startWtime[tid] = omp_get_wtime();
if (tid==0) {
gettimeofday(&sinnertime,0);
}
#pragma omp for nowait
for (itime=0; itime<numkeys; ++itime) {
++(counter[tid]);
// node is a tail, or owned node.
// This is step 1.
gettimeofday(&b1time,0);
node = vec_keys.at(itime);
gettimeofday(&e1time,0);
dt1time[tid] += (1000000 * (e1time.tv_sec - b1time.tv_sec) +
e1time.tv_usec - b1time.tv_usec);
// This is step 2.
gettimeofday(&b1time,0);
p_nim = p_dmb->getModel(node);
gettimeofday(&e1time,0);
dt2time[tid] += (1000000 * (e1time.tv_sec - b1time.tv_sec) +
e1time.tv_usec - b1time.tv_usec);
// This is step 3.
gettimeofday(&b1time,0);
isStateUpdate = p_nim->computeNextState(lots of args);
gettimeofday(&e1time,0);
dt3time[tid] += (1000000 * (e1time.tv_sec - b1time.tv_sec) +
e1time.tv_usec - b1time.tv_usec);
if (isStateUpdate) {
++(countChange[tid]);
}
} // End FOR over vector of owned nodes.
endWtime[tid] = omp_get_wtime();
if (tid==0) {
gettimeofday(&einnertime,0);
}
} // End pragma on OMP parallel.
现在,问题。以8线程执行为例。执行结果如下所示。这些是典型的。 dt1是运行上述第一步的累积时间(以秒为单位); dt2是运行上述第二步的累计时间; dt3是运行上述步骤3的累积时间。 cum = dt1 + dt2 + dt3。 countChange是&#34;节点数&#34;步骤3中的更改。有8行数据,每个线程一个(tid = 0是第一行数据,...,tid = 7是最后一行)。此次运行中有800,000个“节点”,因此最多可以有8 x 100,000 = 800,000个countChanges。我已经确认每个线程正在处理总计800,000个节点中的100,000个。因此,每个线程的工作 - 就处理的节点数而言 - 是相同的。但是,如下所述,每个线程的计算量并不相同。
+++++++++++++++++++++++++++
dt1 dt2 dt3 cum(s)countChange
0.013292 0.041117 3.10149 3.1559 15
0.009705 0.041273 3.17969 3.23067 21
0.009907 0.040998 3.29188 3.34279 16
0.009905 0.040169 3.38807 3.43814 26
0.023467 0.039489 0.198228 0.261184 100000
0.023945 0.038114 0.187334 0.249393 100000
0.023648 0.042231 0.197294 0.263173 100000
0.021285 0.046682 0.219039 0.287006 100000
按预期,dt1小于dt2。正如预期的那样,两者都小于dt3,因为步骤3涉及计算。但请注意,dt3值的问题:它们的变化超过一个数量级,它们聚集成两组:一组有dt3~3.2,一组有dt3~0.19。此外,执行速度最快的线程是执行最多工作的线程;后四个线程中的每一个都改变所有100,000个值,而前四个线程在15-26个值之间变化(显然数量级小于100,000)。后4个线程执行的工作更多,因为更改节点时会有更多计算。此外,我运行的机器是一个2节点,每个节点4核的计算节点。我期望主线程将是tid = 0并且如果有的话会有更短的时间,但是它在具有更多次数的组中。此外,单线程代码产生暨~11.3秒。现在,11.3 / 8 = 1.41秒。
由于代码执行此循环,而其他类似代码执行此循环,数百万次,理想时间(1.41秒)与最大测量时间(上述3.44秒)之间的差异很大,而且似乎过多。
此外,如果用4个线程而不是8个线程运行上面的例子,那么前两个线程有过多的时间而后两个线程有快速的时间。请参阅以下4线程数据:
+++++++++++++++++++++++++++
dt1 dt2 dt3 cum(s)countChange
0.023794 0.073054 5.41201 5.50886 36
0.018677 0.072956 5.77536 5.86699 42
0.027368 0.066898 0.341455 0.435721 200000
0.026892 0.076005 0.363742 0.466639 200000
同样,前两个和后两个线程之间的差异是一个数量级的时间(~5.5对比~0.4);再一次,运行速度最快的线程做得最多。
这是样本2线程数据。第二个线程执行更多工作 - 更改400,000个节点,而第一个线程仅更改78个节点 - 但运行速度提高了一个数量级(10.8对0.8)。 +++++++++++++++++++++++++++
dt1 dt2 dt3 cum(s)countChange
0.025298 0.144209 10.6269 10.7964 78
0.019307 0.126661 0.619432 0.7654 400000
我已经多次使用OpenMP重复了这个实验,并且在一个组合的OpenMP + MPI代码上,我每次都得到相同的结果(当然,这些值是微调的,但是相同的趋势)。前一半的线程(具有最小tid的线程)运行时间最长,工作量较少。此外,使用gcc 4.7.0,因此OpenMP 3.1给出了相同的结果。
我非常感谢任何帮助,为什么这些线程在执行时间上有如此大的差异,以及我可以做些什么来消除它。
答案 0 :(得分:1)
首先,你真的确定花费更长时间的线程做得更少吗?因为奇怪的是,那些仅处理少数项目的线程总是花费最长的时间。如果是这种情况,您可以尝试考虑以下因素:
虽然没有回答为什么某些线程速度较慢,但您可能想尝试guided
或dynamic
调度循环(例如#pragma omp for nowait schedule(dynamic, 10000)
,当然您想要微调chunk_size
以获得最佳性能),通过使更快的线程承担更多负载,在线程之间更均匀地分配工作负载。
作为旁注:为什么你需要所有这些私有变量,考虑到c ++允许在任何结构化块内部进行变量声明,并且在并行部分内声明的变量无论如何都是私有的。因此,在并行部分内首次使用时声明变量可能是可读性甚至性能的好主意(尽管在这种情况下可能并不特别)。