【问题标题】:Slower code with OpenMP how it can be parallelized?使用 OpenMP 的较慢代码如何并行化?
【发布时间】:2013-03-10 02:46:32
【问题描述】:

此代码使用 OpenMP 时速度较慢。如果没有 OpenMP,我会得到大约 10 秒。使用 OpenMP,我得到大约 40 秒。怎么了?非常感谢朋友们!

for (i=2;i<(nnoib-2);++i){
    #pragma omp parallel for
    for (j=2; j<(nnojb-2); ++j) {
        C[i][j]= absi[i]*absj[j]*
                 (2.0f*B[i][j] + absi[i]*absj[j]*
                 (VEL[i][j]*VEL[i][j]*fat*
                 (16.0f*(B[i][j-1]+B[i][j+1]+B[i-1][j]+B[i+1][j])
                 -1.0f*(B[i][j-2]+B[i][j+2]+B[i-2][j]+B[i+2][j]) 
                 -60.0f*B[i][j]
                 )-A[i][j]));
        c2 = (abs(C[i][j]) > Amax[i][j]);
        if (c2) {
            Amax[i][j] = abs(C[i][j]);
            Ttra[i][j] = t;
        }
    }
}

【问题讨论】:

  • c2 声明在哪里?
  • c2 是一个全局变量。 c2 是整数。谢谢!
  • 然后将其设为本地,因为c2 上会有竞争条件/依赖关系。
  • 好的!我将会!可以是 "if (abs(C[i][j]) > Amax[i][j])" 吗?

标签: c++ performance parallel-processing openmp icc


【解决方案1】:

仅仅因为您使用的是 OpenMP 并不意味着您的程序会运行得更快。这里可能会发生几件事:

  1. 产生每个线程都会产生相关成本,如果产生一个线程来进行少量计算,那么线程本身的产生将比计算花费更多的时间。

    李>
  2. 默认情况下,OpenMP 将生成 CPU 支持的最大线程数。对于每个内核支持 2 个或更多线程的 CPU,线程将竞争每个内核的资源。使用omp_get_num_threads(),您可以看到默认情况下会产生多少线程。我建议尝试使用 omp_set_num_threads() 以该值的一半运行您的代码。

您是否确认使用和不使用 OpenMP 的结果相同?似乎与变量 j 和 c2 存在依赖关系。您应该将它们声明为每个线程的私有:

#pragma omp parallel for private(j,c2)

我想补充一点:在尝试任何并行化之前,您应该确保代码已经优化。

根据您的编译器、编译器标志和指令的复杂性,编译器可能会也可能不会优化您的代码:

// avoid calculation nnoib-2 every iteration
int t_nnoib = nnoib - 2;
for (i=2; i< t_nnoib; ++i){
    // avoid calculation nnojb-2 every iteration
    int t_nnojb = nnojb - 2;
    // avoid loading absi[i] every iteration
    int t_absi = absi[i];
    for (j=2; j< t_nnojb; ++j) {
        C[i][j]= t_absi * absj[j] *
             (2.0f*B[i][j] + t_absi * absj[j] *
             (VEL[i][j] * VEL[i][j] * fat *
             (16.0f * (B[i][j-1] + B[i][j+1] + B[i-1][j] + B[i+1][j])
              -1.0f * (B[i][j-2] + B[i][j+2] + B[i-2][j] + B[i+2][j]) 
              -60.0f * B[i][j]
             ) - A[i][j]));

        // c2 is a useless variable
        if (abs(C[i][j]) > Amax[i][j]) {
            Amax[i][j] = abs(C[i][j]);
            Ttra[i][j] = t;
         }
    }
}

它可能看起来不多,但它会对您的代码产生巨大影响。编译器将尝试将局部变量放在寄存器中(访问时间要快得多)。请记住,您不能无限期地应用此技术,因为您的寄存器数量有限,滥用此技术会导致您的代码遭受寄存器溢出。

对于数组absi,您将避免在j 循环执行期间让系统将该数组的一部分保存在缓存中。这种技术的总体思路是将任何不依赖于内循环变量的数组访问移至外循环。

【讨论】:

  • 好的!我将进行修改。 i 等于 1000,j 也等于 1000。非常感谢朋友!
【解决方案2】:

除了 Cristiano 提到的成本之外,您选择在 j 循环上而不是在i 循环上进行并行化会在分配的三个数组中造成错误共享的风险, C, Amax, Ttra。本质上,当一个线程写入其中一个数组的元素时,同一高速缓存行上的连续元素也将被加载到该核心的高速缓存中。当另一个核心将自己的值写入不同的条目时,它必须从另一个缓存中拉出线路,多个核心可能会进行“拔河”。

解决这个问题的方法是在i 上并行化外循环,而不是在j 上并行化内循环。方便的是,这也大大降低了克里斯蒂亚诺回答中提到的成本,因为生成和工作分配只会发生一次,而不是通过i 循环的每次迭代。您仍然需要将jc2 私有化,或者只是在随后的if 中内联c2 的值并消除变量(如您的评论中所述)。为了提高效率,使用本地声明的变量而不是 j 意味着不必访问线程私有变量。

作为一个(相当重要的)检查,这个循环嵌套实际上是您测量的大部分时间的程序部分?添加 OpenMP pragma 将其时间从 10 秒以下更改为 40 秒以下?

【讨论】:

  • 请注意,在此答案的第 2 段中,OpenMP 实现通常不会在执行期间动态创建和销毁线程,而是在启动时创建线程并在完成时销毁它们。这符合 OpenMP 最初设计的一般用途,它不是一个通用的线程系统。
  • 好的实现会在语义与程序员的要求相匹配时做到这一点。即使这样,在每次迭代中将工作从主线程传递给工作线程时仍然存在开销(可能不可忽略,具体取决于调度策略)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-04-10
  • 1970-01-01
  • 1970-01-01
  • 2021-08-05
  • 2021-08-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多