使用OpenMP的较慢代码如何并行化

Slower code with OpenMP how it can be parallelized?

本文关键字:并行化 代码 OpenMP 使用      更新时间:2023-10-16

使用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;
        }
    }
}

使用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循环期间在缓存中保留该数组的一部分。这种技术的一般思想是将任何不依赖于内循环变量的数组访问移到外循环。

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

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

作为一个(相当重要的)检查,这个循环巢实际上是您测量的占用大量时间的程序的一部分?添加OpenMP pragma将其时间从不到10秒缩短到不到40秒?