【问题标题】:OpenMP For-Loops yield different Results with and without MultithreadingOpenMP For-Loops 在有和没有多线程的情况下产生不同的结果
【发布时间】:2021-10-31 01:34:04
【问题描述】:

我是多线程新手,在尝试并行化一些 for 循环时发现了以下问题,我在其中操作 3D 数组。 当我只使用一个线程运行代码时,我得到了我期望的E_total 的值。但是,当我将相同的代码与 多线程 和 OpenMP 一起使用时,我通过以下方式设置 #pragma omp parallel for

    // DO FIRST COMPUTATION STEP ON 3D ARRAY
    #pragma omp parallel for
    for (size_t ix = 0; ix < N; ix++) {
        for (size_t iy = 0; iy < N; iy++) {
            for (size_t iz = 0; iz < N; iz++) {
                A1[ix][iy][iz] = ...;
            }
        }
    }

    // DO SECOND COMPUTATION --AFTER-- FIRST COMPUTATION
    #pragma omp parallel for
    for (size_t ix = 0; ix < N; ix++) {
        for (size_t iy = 0; iy < N; iy++) {
            for (size_t iz = 0; iz < N; iz++) {
                A2[ix][iy][iz] = ...;
                E_pot += something * A1[ix][iy][iz];
                E_int += something * A2[ix][iy][iz];
            }
        }
    }
    E_total += (E_pot + E_int);    // This result changes when 'omp parallel for' is used

我发现对于E_total,我得到了不同的结果。 由于循环操作是附加的或特定于网格点的(独立于不同的ijk),因此它们不应依赖于循环内的任何顺序。

是否有可能在所有先前的第一个循环操作完成之前启动第二个 for 循环?如果是这样,我该如何防止这种情况发生,或者我需要注意哪些其他错误?

对不起,如果这是一个非常基本的问题,但我在网上找不到相关问题。提前致谢!

【问题讨论】:

  • 我认为您在 E_potE_int 两个变量上存在竞争条件。请在第二个 parallel for 构造中添加 reduction(+:E_pot)reduction(+:E_int)
  • 非常感谢,效果很好!您想重新发布您的评论作为答案,以便我接受它作为解决方案吗?

标签: c++ multithreading loops parallel-processing openmp


【解决方案1】:

这段代码的问题是不同线程之间存在竞争条件。 E_potE_int 变量在工作线程之间共享,因此线程会不时破坏彼此的值。

要解决此问题,请应用 reduction 子句(请参阅 OpenMP API 规范中的 Reduction Clauses and Directives):

// DO SECOND COMPUTATION --AFTER-- FIRST COMPUTATION
#pragma omp parallel for reduction(+:E_pot) reduction(+:E_int)
for (size_t ix = 0; ix < N; ix++) {
    for (size_t iy = 0; iy < N; iy++) {
        for (size_t iz = 0; iz < N; iz++) {
            A2[ix][iy][iz] = ...;
            E_pot += something * A1[ix][iy][iz];
            E_int += something * A2[ix][iy][iz];
        }
    }
}

您可以查看更多更改,看看它们是否有帮助:

根据N 的值,可能值得在parallel for 指令中添加collapse(2) 子句(请参阅Worksharing-Loop Construct),以将两个外部循环合并为一个循环,然后运行N*N迭代。对于小的N,线程可以更好地工作,因为更多的迭代可以分布在工作线程中。

如果您明确添加schedule(static)(这是大多数OpenMP 实现的默认设置,但在技术上并不能保证),那么您可以将nowait 添加到第一个循环中。这样一来,在第一个并行循环结束时就没有隐含的障碍,并且已经完成其工作块的线程可以继续进行第二个循环。 schedule(static) 是必需的,因为这样第一个和第二个循环都具有相同的并行化,然后这个技巧就起作用了。注意:如果你为第一个循环添加了collapse(2),那么第二个循环也需要有collapse(2),这样并行化是一样的。

【讨论】:

  • 补充一句:在这种情况下,将 E_pot 和 E_int 声明为原子变量也可以解决这个问题,(这是标准的 C++ 方式)。它还可能获得更好的性能。
  • 很抱歉,您关于性能的说法不正确。 E_potE_int 是高度竞争的变量,它们被工作线程不断修改。该代码将有效地运行接近序列化。在这种情况下不要使用原子变量。这是一种标准的归约模式,它可能总是比原子变量提供更好的性能。
  • 数学是这样的:E_potE_int 是原子变量,你会得到 2*N^3 个原子访问,它们都相互冲突,因此触发缓存一致性协议。使用reduction 子句,您将获得 2*N^3 本地 (!) 缓存访问,编译器甚至可能将这两个变量保存在寄存器中(因此根本没有内存访问)。然后为了减少,您将获得大约 2*log(t)(t:线程数)或 2*t 原子更新。
  • 感谢您的信息。我对 OpenMP 不熟悉,我只是在考虑诸如锁或范围保护之类的东西。线程本地缓存的优化很棒!
  • 谢谢!这是非常有见地的。我也尝试过使用 collapse() 函数,但我不知道也需要将它添加到第二个循环中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-01-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多