为什么OpenMP版本较慢?

12

我正在尝试使用OpenMP。我编写了一些代码来检查它的性能。在一个带有Kubuntu 11.04的4核单个Intel CPU上,使用OpenMP编译的以下程序比不使用OpenMP编译的程序慢约20倍。为什么?

我使用g++ -g -O2 -funroll-loops -fomit-frame-pointer -march=native -fopenmp进行编译。

#include <math.h>
#include <iostream>

using namespace std;

int main ()
{
  long double i=0;
  long double k=0.7;

  #pragma omp parallel for reduction(+:i)
  for(int t=1; t<300000000; t++){       
    for(int n=1; n<16; n++){
      i=i+pow(k,n);
    }
  }

  cout << i<<"\t";
  return 0;
}

4
我从未使用过openMP,但我认为创建多个线程并在这些线程之间同步共享数据访问的开销比将处理分布到4个不同核心上获得的收益要大得多。 - João Portela
但是20次似乎有点过分了。 - Christian Rau
如果您想检查OpenMP性能,那么使用更好设计的可并行化代码是一个不错的选择。 - Steve Townsend
1
我刚拿了你的程序,在一个有2个处理器的Ubuntu 11.04系统上运行了一下。我进行了简单的编译(g++ 4.5.2没有选项)和使用OpenMP的编译(g++ -fopenmp),并运行它们。串行程序的运行时间为6:45.41,而在2个处理器上运行的OpenMP程序则只用了3:36.61(使用time来测量)。考虑到你的程序,这是我所期望的。我会尝试你的选项,看看会发生什么。 - ejd
同意ejd的观点。在4核机器上,使用问题中提到的选项和gcc 4.8,我看到了大约4倍的OpenMP加速。开销非常小。 - Sameer
3个回答

16

问题在于变量k被视为共享变量,因此必须在线程之间进行同步。

避免这种情况的一种可能解决方案是:

#include <math.h>
#include <iostream>

using namespace std;

int main ()
{
  long double i=0;

#pragma omp parallel for reduction(+:i)
  for(int t=1; t<30000000; t++){       
    long double k=0.7;
    for(int n=1; n<16; n++){
      i=i+pow(k,n);
    }
  }

  cout << i<<"\t";
  return 0;
}

根据下面评论中Martin Beckett的提示,你可以在循环外部将k声明为const,而不是在循环内部声明。

否则,ejd是正确的- 这里的问题似乎不是并行化不好,而是当代码并行化时优化不好。请记住,gcc的OpenMP实现还很年轻,远未达到最佳状态。


2
我不会感到惊讶,如果编译器在非 OMP 情况下完全优化掉了所有对 pow() 的调用和内部循环,因为它可以证明 k 是常数,并且具有 16 次迭代的循环在默认展开深度内。最近版本的 gcc 在编译时没有任何精度损失地轻松评估 double 类型的长计算。 - Damon
谢谢olenz。将k的声明移动到循环内部解决了速度问题。但是,它需要在循环内部重新声明k 30000000次。我尝试了另一种解决方案,保持k的声明在循环之前(就像原始代码一样),并将OpenMP代码更改为“#pragma omp parallel for firstprivate(k) reduction(+:i)”,因此k不再共享。然而,它没有起作用。即使k是firstprivate,程序仍然比原来慢20倍。为什么? - Duncan
将“k”设为私有对我的运行结果没有影响(这很合理,因为它从未被更改)。查看生成的代码,串行情况下进行了不同于OpenMP版本的优化。这就是造成性能差异的原因。OpenMP版本仍在运行时执行所有计算,而串行版本在编译时执行了大部分工作。已经做了更多的工作来优化串行代码而不是并行代码(因此有些情况下串行运行比并行运行更快 - 即使并行代码可以更好地优化也没有理由)。 - ejd
1
看起来-funroll-loops优化选项在OpenMP上不起作用,即使在循环之前声明了k并将其设置为私有。也许这是g++的限制?我想知道Intel的icc编译器是否可以进行优化。 - Duncan
4
如有可能,将所有内容都声明为const是值得的 - 这样OMP同步器就知道它不必做任何事情。另请参阅32个C++程序员的OMP陷阱(http://www.viva64.com/en/a/0054/)。 - Martin Beckett
尝试使用 -O3 -ffast-math。 - Demi

3

最快的代码:

for (int i = 0; i < 100000000; i ++) {;}

稍微慢一些的代码:
#pragma omp parallel for num_threads(1)
for (int i = 0; i < 100000000; i ++) {;}

代码运行速度慢2-3倍:

#pragma omp parallel for
for (int i = 0; i < 100000000; i ++) {;}

无论在{和}之间是简单的;还是更复杂的计算,结果都相同。 我在Ubuntu 13.10 64位下使用gcc和g ++编译,尝试不同的参数-ansi-pedantic-errors-Wall-Wextra-O3,并在Intel四核3.5GHz上运行。
我想线程管理开销有问题? OMP似乎并不聪明,每次需要一个线程就创建一个线程,然后在销毁它后销毁。 我以为会有四个(或八个)线程在需要时运行或睡眠。

0

我在 GCC 上观察到类似的行为。但是我想知道是否与模板或内联函数有关。你的代码也在模板或内联函数中吗?请看这里

但是对于非常短的 for 循环,您可能会观察到一些与线程切换相关的小开销,就像在您的情况下一样:

#pragma omp parallel for
for (int i = 0; i < 100000000; i ++) {;}

如果你的循环执行时间非常长,可能是几毫秒甚至几秒钟,当使用OpenMP时,你应该会观察到性能提升。但前提是你有多个CPU。你拥有的核心越多,使用OpenMP时性能就会更高。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接