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

时间:2013-03-10 02:46:33

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

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

2 个答案:

答案 0 :(得分:3)

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

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

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

  3. 您是否确认在使用和不使用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循环期间让系统在缓存中保留该数组的一部分。这种技术的一般思想是将任何数组访问转移到外部循环,而不依赖于内部循环的变量。

答案 1 :(得分:2)

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

对此的解决方案是将外环并行化i而不是j上的内环。方便的是,这也大大降低了Cristiano答案中提到的成本,因为产生和工作任务只会发生一次,而不是通过i循环的每次迭代。您仍需要将jc2私有化,或者只是在后续c2中内联if的值并删除变量(如评论中所述)。为了提高效率,使用本地声明的可变而不是j意味着不必访问线程私有变量。

就像(相当重要的)检查一样,这个循环嵌套实际上是你测量过大部分时间的程序部分吗?添加OpenMP pragma的时间从不到10秒变为不到40秒?