5

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

...
4

6 回答 6

6

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

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

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

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

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

让我知道事情的后续。

于 2011-05-10T00:47:38.077 回答
2

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

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

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

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

尽管如此,结果可能会因操作系统/编译器/平台而异。

请参见使用 OpenMPSolaris 应用程序编程

于 2009-05-17T06:19:27.343 回答
1

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

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

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

于 2009-05-16T18:31:38.290 回答
1

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

于 2009-05-15T21:42:55.267 回答
1

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

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

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

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

于 2009-06-18T02:17:13.990 回答
0

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

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

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

于 2009-05-22T13:06:15.440 回答