可怕的性能 - 一个简单的开销问题,还是存在程序缺陷?

时间:2009-05-15 21:30:32

标签: parallel-processing openmp race-condition overhead

我在这里了解到的是一个相对简单的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;            

...

6 个答案:

答案 0 :(得分:6)

首先,我不认为在这种情况下优化您的序列代码将有助于回答您的OpenMP困境。别担心。

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

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

  2. 产卵工作的开销高于并行效益。你尝试过少于8个核心吗? 2核的性能如何?

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

  4. 让我知道它是怎么回事。

答案 1 :(得分:2)

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

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

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

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

但结果可能因操作系统/编译器/平台而异。

请参阅 Using OpenMP Solaris Application Programming

答案 2 :(得分:1)

我相信你应该尝试移出循环内的所有分支(即ifs),并在两个独立的循环中进行,一个用于i&lt; c,一个用于i&gt; C。 这甚至会对单线程代码产生很大的好处,但是它应该会给你更多的并行性,即使你说线程创建开销可能大于小n的好处。

答案 3 :(得分:1)

Metiu是对的。你不能期望从包含条件语句的循环中获得良好的性能。这只是糟糕的编码。即使是标量性能。

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

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

答案 4 :(得分:1)

首先,尝试更大的块大小。线程创建带来了开销,为每个线程也开始了新的工作,并且粒度需要足够大才能压倒它。

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

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

另一种可能性: 您可能在更新ee_t元素时遇到错误共享。确保此数组对齐,并尝试大小为缓存行大小的倍数的块大小。这个病理学的一个微妙暗示是循环的一部分i > ci < c的部分不成比例地长。

答案 5 :(得分:0)

这看起来像GNU编译器的openmp实现的问题。尝试不同的编译器。英特尔有一个Linux编译器,你应该可以下载它的副本并在这里试试。

我注意到的另一件事是你看到的第一个私有变量似乎是非常不必要的。制作阵列V_in的私有副本可能会有很大的开销,这可能是你的问题。

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