gpt4 book ai didi

c++ - C++和OpenMP-线程的执行时间大不相同

转载 作者:太空宇宙 更新时间:2023-11-04 12:06:03 24 4
gpt4 key购买 nike

我在OMP线程方面遇到问题,因为并行部分中的线程执行时间非常不同。
我在Linux群集上运行。该代码是纯OpenMP代码;例如,没有MPI代码混在一起。它是用C++编写的。
我正在使用gcc 4.5.3,因此使用的是OpenMP 3.0。我正在使用编译优化级别2;即-O2
我先给出代码,然后给出从中生成的数据。
由于我想循环映射的键,因此我首先将键复制到 vector vec_keys中,然后对vec_keys的元素进行并行for循环。
有一个与OMP并行化的for循环。必须处理80万个“节点”或条目。并行for循环中有3个“命令”。

  • 获取基于键的 map 迭代器。参见以下行:node = vec_keys.at(itime);
  • 使用步骤1中的迭代器,获得指向C++对象的指针,该对象将对其调用方法。参见行:p_nim = p_dmb-> getModel(node);
  • 在步骤2中对从 map 检索到的对象调用方法。请参见以下语句:
    isStateUpdate = p_nim-> computeNextState(day,迭代,
    fsmId,p_dmb,tid,节点,
    p_np,p_ep,p_cp,p_fg,newStateNp,
    outStream);

  • 请注意,在步骤2中,检索到 map 条目,但未将其写入。
    在第3步中, map 的内容会更改,但要通过间接更改。即, map 键不变。值(在映射条目中)是指向在堆上实例化的基本数组的指针。因此,通过不更改值指针,我可以更改基本数组的内容。关键是我正在使用 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是在步骤3中更改的“节点”数的计数。共有8行数据,每个线程一行(tid = 0是数据的第一行,…,tid = 7是最后一行)。此运行中有800,000个“节点”,因此最多可以有8 x 100,000 = 800,000个countChanges。我已经确认,每个线程正在处理总计800,000个节点中的100,000个。因此,每个线程的工作(就要处理的节点数而言)都是相同的。但是,如下所述,每个线程的计算量并不相同。

    +++++++++++++++++++++++++++++

    dt1 dt2 dt3暨(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个数量级)。后四个线程执行更多的工作,因为更改节点时会有更多的计算。此外,我正在运行的计算机是2节点,4核/节点的计算节点。我希望主线程将是tid = 0,并且时间更短(如果有的话),但是它在具有更大时间的组中。而且,单线程代码产生的时间约为〜11.3秒。现在,11.3 / 8 = 1.41秒。

    由于代码执行此循环以及其他类似的循环,因此执行了数百万次,因此理想时间(1.41 s)与最大测量时间(上述3.44 s)之间的差异很大,而且似乎过大。

    此外,如果以4个线程而不是8个线程运行上述示例,则前两个线程的时间过多,而后两个线程的时间很快。请参阅以下4线程数据:

    +++++++++++++++++++++++++++++

    dt1 dt2 dt3暨(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线程数据。第二个线程完成了更多的工作-更改了40万个节点,而第一个线程仅更改了78个节点-但运行速度提高了一个数量级(10.8 vs 0.8)。
    +++++++++++++++++++++++++++++

    dt1 dt2 dt3暨(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也会得到相同的结果。

    对于这些线程为何在执行时间上有如此大的差异,以及如何消除这些差异,我将不胜感激。

    最佳答案

    首先,您真的确定花费较长时间的线程会减少工作量吗?因为奇怪的是,那些仅在少数几个项目上起作用的线程总是花费最长时间。如果是这种情况,您可以尝试考虑以下因素:

  • 是否存在错误共享(几个线程访问相同的缓存行,至少其中一些正在编写)?
  • 您提到它在2节点计算机上工作。您可以尝试查看哪个节点执行哪个线程,以及在哪些地方分配内存。除非节点之间的互连确实很糟,否则我怀疑这是问题所在。

  • 尽管无法回答为什么某些线程速度较慢的原因,但您可能想尝试 guideddynamic循环调度(例如 #pragma omp for nowait schedule(dynamic, 10000),当然,您希望微调 chunk_size以获得最大性能),以便将工作负载更均匀地分配到整个进程通过使速度更快的线程可以承担更多的负载。

    附带说明一下:考虑到c++允许在任何结构化块内声明变量,而在并行部分内声明的变量仍然是线程专有的,为什么还要使用所有这些私有(private)变量呢?因此,在并行部分内部首次使用时声明变量可能对可读性乃至性能而言是个好主意(尽管在这种情况下不太可能)。

    关于c++ - C++和OpenMP-线程的执行时间大不相同,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12044407/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com