是全局变量的只读和写入私人类成员"虚假共享"在openmp中

时间:2014-05-19 06:33:50

标签: c++ openmp

我正在编写一个程序,其中一堆存储在向量中的不同类使用公共数据结构对私有成员执行并行操作。我想使用OpenMP将其并行化为多个处理器,但我对代码中的两个操作有两个问题,这两个问题都在下面的示例中的注释中指出,这些注释显示了程序逻辑的简化形式。

#include <omp.h>
#include <iostream>
#include <sys/timeb.h>
#include <vector>

class A {
  private :
    long _i;
  public :
    void add_i(long &i) { _i += i; }
    long get_i() const { return _i; }
};

int main()
{
  timeb then;
  ftime(&then);
  unsigned int BIG = 1000000;
  int N = 4;
  std::vector<long> foo(BIG, 1);
  std::vector<A *> bar;
  for (unsigned int i = 0; i < N; i++)
  {
    bar.push_back(new A());
  }
  #pragma omp parallel num_threads(4)
  {
    for(long i = 0; i < BIG; i++)
    {
      int thread_n = omp_get_thread_num();
      // read a global variable
      long *to_add = &foo[i];
      // write to a private variable
      bar[thread_n]->add_i(*to_add);
    }
  }
  timeb now;
  ftime(&now);
  for (int i = 0; i < N; i++)
  {
    std::cout << bar[i]->get_i() << std::endl;
  }
  std::cout << now.millitm - then.millitm << std::endl;
}

第一条评论解决了全局foo的读取问题。这是“虚假共享”(或数据晃动)吗?我读到的大多数资源都谈到了写操作方面的错误共享,但我不知道读操作是否同样适用。

第二条注释解决了对bar中的类的写操作。同样的问题:这是假共享吗?他们正在写同一个全局数据结构中的元素(也就是我所读过的,晃动),但只是在元素中对私有数据进行操作。

当我用for循环替换OpenMP宏时,程序速度提高了大约25%,所以我猜我做错了......

2 个答案:

答案 0 :(得分:1)

现代内存分配器是线程感知的。为了防止在修改class A元素所指向的bar的每个实例时发生错误共享,您应该在并行区域内移动内存分配,例如:

const int N = 4;
std::vector<long> foo(BIG, 1);
std::vector<A *> bar(N);
#pragma omp parallel num_threads(N)
{
  int thread_n = omp_get_thread_num();
  bar[thread_n] = new A();
  for(long i = 0; i < BIG; i++)
  {
    // read a global variable
    long *to_add = &foo[i];
    // write to a private variable
    bar[thread_n]->add_i(*to_add);
  }
}

另请注意,在这种情况下,omp_get_thread_num()只会被调用一次而不是代码中的BIG次。调用函数的开销相对较低,但是当你多次这样做时会增加。

答案 1 :(得分:0)

您最大的共享问题是bar[thread_n]。阅读foo[i]不是一个问题。

编辑:由于bar[thread_n]持有指针,并且指针对象是更新的,因此很少或没有共享。您可能仍然可以从一次加载&#34;肿块中受益。进入每个CPU核心,而不是从每个高速缓存行读取每个CPU核心的一个或两个项目。所以下面的代码可能仍然有益。一如既往的性能问题,很多基准测试(启用优化),因为不同的系统会表现不同(取决于编译器,CPU架构,内存子系统等等)

最好是&#34;疙瘩&#34;每个帖子中一次有几个项目。这样的事情,也许是:

const int STEP=16;

for(long i = 0; i < BIG; i+=STEP)
{
  int thread_n = omp_get_thread_num();
  int stop = std::min(BIG-i, STEP);   // Don't go over edge. 
  for(j = 0; j < stop; j++)
  {
     // read a global variable    
     long *to_add = &foo[i+j];
     // write to a private variable
     bar[thread_n*STEP + j]->add_i(*to_add);
  }
}

您可能需要调整&#34; STEP&#34;有点让它成为正确的水平。