【问题标题】:Why is OpenMP slow in this case?为什么在这种情况下 OpenMP 很慢?
【发布时间】:2011-03-22 19:54:18
【问题描述】:

我试图理解为什么 OpenMP 会按照以下示例中的方式工作。

#include <omp.h>
#include <iostream>
#include <vector>
#include <stdlib.h>

void AddVectors (std::vector< double >& v1,
                 std::vector< double >& v2) {

    size_t i;

#pragma omp parallel for private(i)
    for (i = 0; i < v1.size(); i++) v1[i] += v2[i];

}


int main (int argc, char** argv) {

    size_t N1 = atoi(argv[1]);

    std::vector< double > v1(N1,1);
    std::vector< double > v2(N1,2);

    for (size_t i = 0; i < N1; i++) AddVectors(v1,v2);

    return 0;

}

我首先在没有启用 OpenMP 的情况下编译了上面的代码(通过在编译标志上省略 -fopenmp)。 N1 = 10000 的执行时间为 0.1 秒。启用 OpenMP 会使执行时间超过 1 分钟。我在它完成之前停止了它(厌倦了等待......)。

我正在编译如下代码:

g++ -std=c++0x -O3 -funroll-loops -march=core2 -fomit-frame-pointer -Wall -fno-strict-aliasing -o main.o -c main.cpp

g++ main.o -o main

这里并非所有这些标志都是必需的,但我正在尝试并行化的项目中使用它们,并且在那里使用这些标志。这就是为什么我决定把他们留在这里。另外,我添加了 -fopenmp 以在编译时启用 OpenMP。

有人知道出了什么问题吗?谢谢!

【问题讨论】:

  • 只是一个猜测,但是:您是否尝试将循环最大值 (v1.size()) 存储在循环之前的临时变量中,并在 for-loop 子句中使用该变量?也许编译器看不到 size() 的返回值由于某种原因没有改变。
  • 您的代码现在看起来很奇怪。 N1 作为向量的长度和循环的运行次数?这是故意的吗?
  • 无法重现,在这里可以正常工作(N1=100000,17s 没有 openmp,4.7s 使用 Core2 Quad)。你有什么版本的 GCC?什么类型的CPU
  • Ronny:我之前有一个错误——我将 N1 和 N2 设置为 atoi(argv[1]) 所以我放弃了 N2。看起来很奇怪,但不会改变我的结果。
  • 好吧,WorksForMe(tm) 与 G++ 4.5.1 (Gentoo)。无关:C++ 有一个&lt;cstdlib&gt; 标头,我认为它比&lt;stdlib.h&gt; 更“合适”。

标签: c++ openmp


【解决方案1】:

我在 Visual Studio 2008 上尝试过相同的示例。 我对您的代码示例进行了两次修改,使用 OpenMP 的运行速度大约比不使用 OpenMP 的速度快 3 倍。

如果无法在 GCC 上确认,问题可能在主循环中调用了函数 AddVectors,并且每次它必须执行“fork”操作,这将花费一些可测量的时间。所以如果你有 N1 = 10000,它必须产生 10000 个“fork”操作。

我附上了你自己的代码 sn-p 修改只是为了使它在 Visual Studio 下工作, 最后我添加了一个打印语句以避免编译器删除所有代码。

#include <omp.h>
#include <iostream>
#include <vector>
#include <stdlib.h>

void AddVectors (std::vector< double >& v1,
                 std::vector< double >& v2) {

    #pragma omp parallel for
    for (int i = 0; i < static_cast<int>(v1.size()); i++) v1[i] += v2[i];

}


int main (int argc, char** argv) {

    size_t N1 = atoi(argv[1]);

    std::vector< double > v1(N1,1);
    std::vector< double > v2(N1,2);

    for (size_t i = 0; i < N1; i++) AddVectors(v1,v2);


    printf("%g\n",v1[0]);
    return 0;

}

【讨论】:

    【解决方案2】:

    g++ 是否可以优化整个 AddVectors 调用?尝试返回最后一个 v1 元素并将其存储在 volatile 变量中。

    【讨论】:

      【解决方案3】:

      问题在于您使用的数组类型

      向量是一个容器。它是一个存储几个信息的结构,如大小、开始、结束等;并具有多个内置功能,其中 operator [] 是其中之一,用于访问数据。结果,倾向于加载向量V的索引“i”的缓存行,加载元素V[i]和一些未使用的信息在代码中。

      相反,如果您使用经典数组(动态/静态),则运算符 [] 会导致仅加载数据元素。因此,一个高速缓存行(通常为 64 字节长)将加载这个双精度数组的 8 个元素(双精度 = 8 字节)。

      查看 _mm_malloc 和 malloc 之间的区别以增强数据对齐。

      @福兹先生 我不确定。让我们比较两种情况的性能结果:

      i7 处理器上有 4 个线程

      阵列所用时间:0.122007 |重复:4 | MFlops:327.85

      矢量所用时间:0.101006 |重复:2 | MFlops:188.669

      我强制运行时间超过 0.1 秒,所以代码会重复。主循环:

      const int N = 10000000;
      timing(&wcs);
      for(; runtime < 0.1; repeat*=2)
      {
          for(int r = 0; r < repeat; r++)
          {
              #pragma omp parallel for
              for(int i = 0; i < N; i++)
              {
                  A[i] += B[i];
              }
              if(A[0]==0) dummy(A[0]);
          }
          timing(&wce);
          runtime = wce-wcs;
      }
      

      MFLops: ((N*repeat)/runtime)/1000000

      【讨论】:

      • -1:相对于纯 C 数组实现,vector 在大多数情况下是一个 0 开销的容器。额外的元数据(数组的长度和保留大小)与向量实例一起存储,而不是在每个元素中。
      • -1 => +1:你似乎是对的。有趣的。你知道为什么它会加载任何额外的元数据吗? operator[] 不做边界检查。
      • @MrFooz: Check this out
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-23
      • 1970-01-01
      • 2016-08-08
      相关资源
      最近更新 更多