【问题标题】:Terrible performance - a simple issue of overhead, or is there a program flaw?糟糕的性能 - 一个简单的开销问题,还是存在程序缺陷?
【发布时间】:2010-10-26 15:17:43
【问题描述】:

我这里有一个我理解的相对简单的 OpenMP 构造。问题在于,与 2 个线程相比,1 个线程的程序运行速度大约快 100-300 倍。该计划的 87% 用于gomp_send_wait(),另外 9.5% 用于gomp_send_post

程序给出了正确的结果,但我想知道代码中是否存在导致某些资源冲突的缺陷,或者仅仅是线程创建的开销对于块大小的循环来说是完全不值得的4. p 的范围从 17 到 1000,取决于我们模拟的分子的大小。

我的数字是针对最坏情况的,当 p 为 17 且块大小为 4 时。无论我使用静态、动态还是引导式调度,性能都是相同的。使用p=150 和块大小75,程序仍然比串行慢75x-100x。

...
    double e_t_sum=0.0;
    double e_in_sum=0.0;

    int nthreads,tid;

    #pragma omp parallel for schedule(static, 4) reduction(+ : e_t_sum, e_in_sum) shared(ee_t) private(tid, i, d_x, d_y, d_z, rr,) firstprivate( V_in, t_x, t_y, t_z) lastprivate(nthreads)
    for (i = 0; i < p; i++){
        if (i != c){
            nthreads = omp_get_num_threads();               
            tid = omp_get_thread_num();

            d_x = V_in[i].x - t_x; 
            d_y = V_in[i].y - t_y;
            d_z = V_in[i].z - t_z;


            rr = d_x * d_x + d_y * d_y + d_z * d_z;

            if (i < c){

                ee_t[i][c] = energy(rr, V_in[i].q, V_in[c].q, V_in[i].s, V_in[c].s);
                e_t_sum += ee_t[i][c]; 
                e_in_sum += ee_in[i][c];    
            }
            else{

                ee_t[c][i] = energy(rr, V_in[i].q, V_in[c].q, V_in[i].s, V_in[c].s);
                e_t_sum += ee_t[c][i]; 
                e_in_sum += ee_in[c][i];    
            }

            // if(pid==0){printf("e_t_sum[%d]: %f\n", tid, e_t_sum[tid]);}

        }
    }//end parallel for 


        e_t += e_t_sum;
        e_t -= e_in_sum;            

...

【问题讨论】:

  • 您正在运行的系统中有多少个处理器?

标签: parallel-processing openmp race-condition overhead


【解决方案1】:

首先,我不认为在这种情况下优化您的串行代码将有助于解决您的 OpenMP 难题。别担心。

IMO 对减速有三种可能的解释:

  1. 这可以很容易地解释减速。数组 ee_t 的元素导致缓存行内的错误共享。错误共享是指内核最终写入同一缓存行,而不是因为它们实际上共享数据,而是内核正在写入的内容恰好位于同一缓存行中(这就是为什么它被称为 false分享)。如果你在谷歌上没有发现虚假分享,我可以解释更多。使 ee_t 元素缓存行对齐可能会有很大帮助。

  2. 生成工作的开销高于并行性收益。您是否尝试过少于 8 个内核? 2核性能如何?

  3. 总的迭代次数很少,比如说我们以17次为例。如果你把它分成 8 个核心,它会遇到负载不平衡的问题(特别是因为你的一些迭代实际上没有做任何工作(当 i == c 时)。至少一个核心必须进行 3 次迭代,而所有其他的将做2.这并不能解释减速,但肯定是加速没有你想象的那么高的一个原因。由于你的迭代长度不同,我会使用块大小为1的动态调度或使用openmp引导。试验一下 chunk 的大小,chunk 太小也会导致速度变慢。

让我知道进展如何。

【讨论】:

    【解决方案2】:

    您似乎认为,如果您在多头模式下运行串行代码,它必须执行得更好。这不是给定的。而且,这通常不是真的。将循环并行化以在多个线程或多个处理器中运行并不总能带来更好的性能。在大多数情况下,需要进行一些重组。在你的情况下,代码甚至不是好的序列代码。

    任何关于串行代码优化的书都有第 1 条 for 循环规则:删除所有条件操作。测试费用。一些编译器(顺便说一句,你从不说你正在使用什么操作系统/编译器/处理器......这很重要)可以尝试优化条件代码。一些编译器(如 Sun 的 C 编译器)甚至允许您在“收集”模式下运行程序,它会生成运行时配置文件信息,说明获取条件分支的频率,然后让您在使用收集到的数据的模式下重新编译优化生成的代码。 (参见 -xprofile 选项)

    优化并行代码的第一条规则是首先做最好的串行优化。然后并行化循环。

    通过将条件移到循环外,并如 Metiu 建议的那样,将代码重写为两个独立的循环,您可以为优化器提供更好的源代码。串行代码跑得更好,并行化的代码并行的尴尬。

    不过,结果可能会因操作系统/编译器/平台而异。

    请参阅 Using OpenMPSolaris Application Programming

    【讨论】:

    • 再一次,你在谈论一个公认的糟糕但无关紧要的代码问题。事实证明,我在美图发布后立即编写了测试。自然,它的串行速度提高了 15%,并且自然地,使用 OpenMP 具有完全相同的相对性能问题。我在哪里断言所有并行代码运行得更快?我在这种情况下特别询问,因为性能要差得多,而在过去,我已经看到由于错误代码而发生这种情况。既然死马已经被打得够呛,说不定会有其他人进来戳我的问题。
    【解决方案3】:

    我相信您应该尝试移出循环内的所有这些分支(即 ifs),并在两个单独的循环中执行此操作,一个用于 i c。 即使是单线程代码,这也会极大地受益,但它应该会给你更多的并行性,即使正如你所说的线程创建开销可能大于小 n 的好处。

    【讨论】:

    • 感谢您的推荐。我认为你是绝对正确的,它将通过创建两个循环来改进代码以使这两个测试脱离内部循环。我今天肯定会这样做。但是,老板希望看到一个 OpenMP 版本,并且不会因为仅仅删除一些内循环测试而感到安抚。 :)
    【解决方案4】:

    Metiu 是对的。您不能期望包含条件语句的循环具有良好的性能。这只是糟糕的编码。即使是标量性能。

    您的老板需要明白,OpenMP 和并行化通常不是魔法。要从并行代码中获得良好的性能,需要首先针对标量性能优化基本代码。

    不必删除测试。循环需要重组。标量性能也将受益。

    【讨论】:

    • 在我的原始帖子中,我对单线程与多线程的代码性能进行了相对比较。由于“坏”是相对的,我认为可以公平地说线程性能很差 - 相对于串行版本。我确实相信您引用的代码改进示例与 OpenMP 性能问题是正交的,但是如果您愿意解释为什么这种观点是错误的,我很乐意学习新的东西。也许您可以解释“需要重组循环”是什么意思 - 以什么方式?
    【解决方案5】:

    首先,尝试将块大小抽得更大。线程创建会带来开销,为每个线程拾取新工作也会带来开销,并且粒度需要足够大以压倒它。

    一个更大的可能性: GOMP 的减少实现可能很差(由您的配置文件数据建议),它在每个块之后生成消息,而不是在每个线程中累积然后在最后收集它们。尝试将e_t_sume_in_sum 分配为每个带有nthreads 元素的数组,并在循环内添加到e_t_sum[tid],然后在并行循环完成后循环遍历它们以计算全局总和。

    请注意,这会引入潜在的错误共享问题,因为这些数组的多个元素将位于公共缓存线中,并且多个处理器将写入同一缓存线。如果你在一组共享缓存的核心上运行它,这会很好,但在其他地方会很臭。

    另一种可能: 您可能在对 ee_t 元素的更新中遇到错误共享。确保此数组对齐,并尝试将块大小设置为缓存行大小的倍数。这种病态的一个微妙暗示是循环中i &gt; ci &lt; c 花费不成比例的更长的部分。

    【讨论】:

      【解决方案6】:

      这看起来像是 GNU 编译器的 openmp 实现的问题。尝试不同的编译器。英特尔有一个 Linux 编译器,您应该可以下载并在此处试用。

      我注意到的另一件事是您的第一个私有变量似乎完全没有必要。制作数组 V_in 的私有副本可能会产生很大的开销,这可能是您的问题。

      我会说这是你的问题这两个问题之一。

      【讨论】:

        猜你喜欢
        • 2014-09-07
        • 2015-05-11
        • 2021-04-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-26
        • 2013-02-04
        • 2017-04-10
        相关资源
        最近更新 更多