【问题标题】:Why OpenMP version is slower?为什么 OpenMP 版本比较慢?
【发布时间】:2016-04-02 21:16:58
【问题描述】:

我正在试验 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;
}

【问题讨论】:

  • 我从未使用过 openMP,但在我看来,创建多个线程和在这些线程之间同步共享数据访问的开销超过了(很多)将处理分布在 4 个不同的内核上。
  • 不过20次似乎有点太极端了。
  • 如果您正在尝试检查 OpenMP 性能,那么使用设计更好的可并行化代码将是一个好主意。
  • 我刚刚获取了您的程序并在具有 2 个处理器的 Ubuntu 11.04 系统上运行它。我做了一个简单的编译(g++ 4.5.2 没有选项)和一个 OpenMP 编译(g++ -fopenmp)并运行它们。串行程序的运行时间为 6:45.41,而 OpenMP 程序(在 2 个处理器上运行)花费了 3:36.61(使用时间来测量)。考虑到您的程序,这是我所期望的。我会尝试你的选择,看看会发生什么。
  • 同意ejd。我看到在 4 核机器上使用 gcc 4.8 使用 OpenMP(使用问题中提到的选项)的速度提高了大约 4 倍。开销很少。

标签: c++ openmp


【解决方案1】:

问题是变量 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,而不是在循环内声明 k。

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

【讨论】:

  • 那,如果编译器在非 OMP 情况下完全优化了完整的内部循环并调用了 pow(),我不会感到惊讶,因为它可以证明 k 是常量并且具有 16 次迭代的循环在默认展开深度内。最新版本的 gcc 在编译时对 doubles 上的长计算完全没有问题,没有任何精度损失。
  • 谢谢奥伦茨。将 k 的声明移到循环内部解决了速度问题。但是,它需要在循环内重新声明 k 30000000 次。我尝试了不同的解决方案,方法是在循环之前保留 k 的声明(如原始代码)并将 OpenMP 代码更改为“#pragma omp parallel for firstprivate(k) reduction(+:i)”,因此不再共享 k。但是,它没有用。即使 k 是 firstprivate,程序仍然慢 20 倍。为什么?
  • 将“k”设为私有对我的运行没有任何影响(这是有道理的,因为它永远不会改变)。查看生成的代码,串行案例的优化与 OpenMP 版本完全不同。这就是造成巨大性能差异的原因。 OpenMP 版本仍在运行时进行所有计算,而串行版本在编译时进行大量工作。与并行代码相比,在优化串行代码方面做了更多的工作(因此在某些情况下串行运行速度比并行更快 - 即使没有理由不能更好地优化并行代码)。
  • 看起来 -funroll-loops 优化选项在循环之前声明 k 时在 OpenMP 上不起作用,即使 k 设置为私有。也许这是g ++的限制?不知道Intel的icc编译器能不能优化一下。
  • 在可能的情况下声明一切都是 const 也是值得的——这样 OMP 同步器就知道它不需要做任何事情。另请参阅 C++ 程序员的 32 个 OMP 陷阱 (viva64.com/en/a/0054)
【解决方案2】:

最快的代码:

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 在每次需要线程时创建线程并在之后销毁它似乎并不聪明。我认为会有四个(或八个)线程在需要时运行或休眠。

【讨论】:

    【解决方案3】:

    我在 GCC 上观察到类似的行为。但是我想知道在我的情况下它是否与模板或内联函数有关。您的代码是否也在模板或内联函数中?请看here

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

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

    如果您的循环执行了几毫秒甚至几秒的非常长的时间,您应该在使用 OpenMP 时观察到性能提升。但仅当您拥有多个 CPU 时。您拥有的内核越多,OpenMP 的性能就越高。

    【讨论】:

      猜你喜欢
      • 2017-12-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-15
      • 2021-03-27
      • 2013-12-11
      相关资源
      最近更新 更多