OpenMP并行 - 用于效率查询

时间:2017-05-10 16:36:16

标签: c++ openmp

请考虑以下简单代码,用于汇总parallel for循环中的值:

int nMaxThreads = omp_get_max_threads();
int nTotalSum = 0;
#pragma omp parallel for num_threads(nMaxThreads) \
    reduction(+:nTotalSum)
    for (int i = 0; i < 4; i++)
    {
        nTotalSum += i;
        cout << omp_get_thread_num() << ": nTotalSum is " << nTotalSum << endl;
    }

当我在双核机器上运行时,我得到的输出是

0: nTotalSum is 0
0: nTotalSum is 1
1: nTotalSum is 2
1: nTotalSum is 5

这告诉我,关键部分,即nTotalSum的更新,正在每个循环上执行。这似乎是一种浪费,当所有每个线程都要做的是计算它所添加的值的“局部”总和,然后在它完成后用这个“局部和”更新nTotalSum

我对输出的解释是否正确,如果是,我怎样才能提高效率?注意我尝试了以下内容:

#pragma omp parallel for num_threads(nMaxThreads) \
    reduction(+:nTotalSum)
    int nLocalSum = 0;
    for (int i = 0; i < 4; i++)
    {
        nLocalSum += i;
    }
    nTotalSum += nLocalSum;

...但是编译器抱怨说它在for语句之后期待pragma omp parallel for循环......

3 个答案:

答案 0 :(得分:2)

每个OMP线程都有自己的nTotalSum副本。在OMP部分的末尾,这些被组合回原始的nTotalSum。您看到的输出来自在一个线程中运行循环迭代(0,1),以及在另一个线程中运行(2,3)。如果在循环结束时输出nTotalSum,则应该看到预期的结果为6。

nLocalSum示例中,将nLocalSum的声明移至#pragma omp行之前。 for循环必须紧跟在编译指示之后的行上。

答案 1 :(得分:2)

您的输出实际上并不表示循环期间的关键部分。每个线程都有自己的零初始化副本,线程0在i = 0,1上工作,线程1在i = 2,3上工作。最后,OpenMP负责将本地副本添加到原始副本中。

除非您有具体证据证明您可以更有效地执行此操作,否则您不应尝试自己实施。请参阅示例this question / answer

如果将parallel / for拆分为两个指令,您的手动版本就可以使用:

int nTotalSum = 0;
#pragma omp parallel
{
  // Declare the local variable it here!
  // Then it's private implicitly and properly initialized
  int localSum = 0;
  #pragma omp for
  for (int i = 0; i < 4; i++) {
    localSum += i;
    cout << omp_get_thread_num() << ": nTotalSum is " << nTotalSum << endl;
  }
  // Do not forget the atomic, or it would be a race condition!
  // Alternative would be a critical, but that's less efficient
  #pragma omp atomic
  nTotalSum += localSum;
}

我认为您的OpenMP实施可能就是这样做的。

答案 2 :(得分:0)

来自我在openmp书中的并行编程:

  

还原子句可能更难以理解,具有私有和共享存储行为。 reduction属性用于作为算术缩减目标的对象。这在许多应用程序中都很重要...减少允许它有效地由编译器实现...这是一个常见的操作,openmp具有简化数据范围子句只是为了处理它们...最常见的例子是最终求和并行构造末尾的临时局部变量。

修正你的第二个例子:

total_sum = 0;  /* do all variable initialization prior to omp pragma */

#pragma omp parallel for \
            private(i) \
            reduction(+:total_sum)

   for (int i = 0; i < 4; i++)
   {
       total_sum += i;  /* you used nLocalSum here */
   }

#pragma omp end parallel for

/* at this point in the code,
   all threads will have done your `for` loop where total_sum is local to each thread,
   openmp will then '+" together the values in `total_sum` coming from each thread because we used reduction,
   do not do an explicit nTotalSum += nLocalSum after the omp for loop, it's not needed the reduction clause takes care of this
*/

在您的第一个示例中,我不确定您使用#pragma omp parallel for num_threads(nMaxThreads) reduction(+:nTotalSum) num_threads(nMaxThreads)正在做什么。但我怀疑这种奇怪的输出可能是由打印缓冲引起的。

在任何情况下,如果使用得当,减少条款非常有用并且非常有效。在更复杂,更现实的例子中,这将更加明显。

您发布的示例非常简单,它不会显示还原子句的有用性,严格来说就是您的示例,因为所有线程都在进行总和最多效率这样做的方法只是让total_sum成为并行部分中的共享变量,并让所有线程都加入其中。最后,答案仍然是正确的。 如果使用关键指令将会有效。