【问题标题】:Trouble with nested loops and openmp嵌套循环和openmp的问题
【发布时间】:2011-01-19 22:08:35
【问题描述】:

我无法将 openmp 应用于这样的嵌套循环:

        #pragma omp parallel shared(S2,nthreads,chunk) private(a,b,tid)
    {
        tid = omp_get_thread_num();
        if (tid == 0)
        {
            nthreads = omp_get_num_threads();
            printf("\nNumber of threads = %d\n", nthreads);
        }
        #pragma omp for schedule(dynamic,chunk)
        for(a=0;a<NREC;a++){
            for(b=0;b<NLIG;b++){
                S2=S2+cos(1+sin(atan(sin(sqrt(a*2+b*5)+cos(a)+sqrt(b)))));
            }
        } // end for a
    } /* end of parallel section */

当我将串行与 openmp 版本进行比较时,最后一个给出了奇怪的结果。即使我删除了#pragma omp for,openmp 的结果也不正确,你知道为什么或者可以指出一个关于双循环和 openmp 的好教程吗?

【问题讨论】:

    标签: c loops openmp


    【解决方案1】:

    这是race condition 的经典示例。您的每个 openmp 线程都在同时访问和更新一个共享值,并且无法保证某些更新不会丢失(最好的情况)或得到的答案不会是乱码(最坏的情况)。

    竞态条件的问题在于它们敏感地依赖于时间;在较小的情况下(例如,使用较小的 NREC 和 NLIG),您有时可能会错过这一点,但在较大的情况下,它最终总会出现。

    没有#pragma omp for,你得到错误答案的原因是,一旦你进入并行区域,你所有的openmp线程都会启动;除非您使用类似 omp for(所谓的工作共享结构)的东西来拆分工作,否则每个线程将在并行部分执行 所有操作 - 所以所有线程都将执行相同的操作全部金额,全部同时更新S2

    您必须小心 OpenMP 线程更新共享变量。 OpenMP 具有atomic 操作,允许您安全地修改共享变量。下面是一个例子(不幸的是,你的例子对求和顺序非常敏感,很难看出发生了什么,所以我稍微改变了你的总和:)。在mysumallatomic中,每个线程都像以前一样更新S2,但这次是安全完成的:

    #include <omp.h>
    #include <math.h>
    #include <stdio.h>
    
    double mysumorig() {
    
        double S2 = 0;
        int a, b;
        for(a=0;a<128;a++){
            for(b=0;b<128;b++){
                S2=S2+a*b;
            }
        }
    
        return S2;
    }
    
    
    double mysumallatomic() {
    
        double S2 = 0.;
    #pragma omp parallel for shared(S2)
        for(int a=0; a<128; a++){
            for(int b=0; b<128;b++){
                double myterm = (double)a*b;
                #pragma omp atomic
                S2 += myterm;
            }
        }
    
        return S2;
    }
    
    
    double mysumonceatomic() {
    
        double S2 = 0.;
    #pragma omp parallel shared(S2)
        {
            double mysum = 0.;
            #pragma omp for
            for(int a=0; a<128; a++){
                for(int b=0; b<128;b++){
                    mysum += (double)a*b;
                }
            }
            #pragma omp atomic
            S2 += mysum;
        }
        return S2;
    }
    
    int main() {
        printf("(Serial)      S2 = %f\n", mysumorig());
        printf("(All Atomic)  S2 = %f\n", mysumallatomic());
        printf("(Atomic Once) S2 = %f\n", mysumonceatomic());
        return 0;
    }
    

    然而,原子操作确实会损害并行性能(毕竟,重点是防止围绕变量S2!进行并行操作)所以更好的方法是进行求和,并且只在两次求和之后执行原子操作,而不是执行 128*128 次;这就是mysumonceatomic() 例程,每个线程只会产生一次同步开销,而不是每个线程 16k 次。

    但这是一个很常见的操作,没有必要自己实现。可以将 OpenMP 内置功能用于归约操作(归约是一种操作,例如计算列表的总和、查找列表的最小值或最大值等,只需查看到目前为止的结果和下一个元素)如@ejd 所建议的那样。 OpenMP 可以运行并且速度更快(它的优化实现比您自己使用其他 OpenMP 操作可以执行的要快得多)。

    如您所见,两种方法都有效:

    $ ./foo
    (Serial)      S2 = 66064384.000000
    (All Atomic)  S2 = 66064384.000000
    (Atomic Once) S2 = 66064384.00000
    

    【讨论】:

    • 感谢您指出这一点,因为这似乎是问题所在,我现在正在测试它
    • 我很好奇的是,如果你将 a*b 更改为像我这样更复杂的表达式会发生什么,这根本没有问题
    • 我看到即使在我的代码上使用 omp atomic,我也会得到奇怪的结果。有没有办法检查我的程序运行时没有出现竞争条件?我的意思是,要确保在任何情况下两个线程都不会尝试同时修改变量 S2
    • Werner:“奇怪的结果”是什么意思?如果您的意思是您的原始总和变化 +/- 1/2%,那是因为您选择的总和在数值上非常不稳定 - 您得到的答案对您添加数字的顺序非常敏感。即使在纯串行代码中,您也可以通过将获得的总和与相同的总和但颠倒循环的顺序进行比较,或者让循环从 NREC...0 和 NLIG..0 而不是 0..NREC 开始,等等。这是总和和浮点运算的属性,而不是 OpenMP。
    • 在检查竞争条件方面 - 在像这样的小型 sn-ps 代码中,这应该不是什么大问题。在您的并行部分中,设置 default(shared) 以便您必须显式声明每个变量的共享/私有状态,并仔细查看每个共享变量在该部分中是如何更新的。 OpenMP 代码有商业静态分析工具 - VivaMP 就是其中之一,intel 有一些线程分析器工具,intel c/c++ 编译器有 /Qdiag-enable:sc-parallel 用于一些基本检查。
    【解决方案2】:

    问题不在于双循环,而在于变量 S2。尝试在你的 for 指令上添加一个缩减子句:

    #pragma omp for schedule(dynamic,chunk) reduction(+:S2)

    【讨论】:

    • 非常感谢。知道我得到了更好和更一致的结果,尽管 openmp 最终 S2 结果与串行版本大约有 1% 的差异,并且每次运行程序时,S2 的 openmp 结果都会发生变化。可能是什么原因?
    • 这里变量归约的形式和确切含义是什么?
    • 减少可以采取多种形式。您可以查看 OpenMP V3.0 规范的第 2.9.3.6 节缩减子句,以获取一些典型形式的列表。在您的情况下,形式是: var += 表达式。至于运行之间的差异,计算的顺序与串行顺序不同。我不知道你的循环范围是什么或你的分块因素,所以很难说更多。但是,如果您将大数字添加到小数字与大数字和大数字相加,则可能会丢失一些有效数字。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-27
    • 1970-01-01
    • 2021-01-10
    • 1970-01-01
    相关资源
    最近更新 更多