提问者:小点点

OpenMP reduction子句对于循环计数的大长int不起作用


下面有一段代码:

#include <iostream>
#include <fstream>
#include <limits>
#include <chrono>
#include <omp.h>

const long int N=1000000000L;

class Counter {
public:
  Counter():n(0),N(0){}
  void operator()()
  {
    if(n==INT_MAX)
    {
  #pragma omp atomic update
      ++N;
  #pragma omp atomic write
      n=1;
    }
    else
    {    
  #pragma omp atomic update
      ++n;
    }
  }
  long int GetTotalCount()
  {
    return (static_cast<long int>(n)+static_cast<long int>(N)*static_cast<long int>(INT_MAX));
  }
  friend std::ostream & operator<<(std::ostream & o, Counter & counter)
  {
    o<<counter.GetTotalCount()<<" ("<<counter.N<<","<<counter.n<<") = "
     <<static_cast<long int>(counter.N)*static_cast<long int>(counter.INT_MAX)+static_cast<long int> 
       (counter.n)
 <<std::endl;
return o;
  }
private:
  const int INT_MAX=std::numeric_limits<int>::max();
  int n;
  int N;
};

int main(int argc, char **argv)
{
  const auto begin = std::chrono::steady_clock::now();
  Counter counter;
#pragma omp parallel for simd
  for(long int i=0; i<N; ++i)
  {
    if(i%2==0) counter();
  }
  const auto end = std::chrono::steady_clock::now();
  std::cout<<"N="<<N<<std::endl;
  std::cout<<"Total count="<<counter;
  auto time = std::chrono::duration_cast<std::chrono::milliseconds>(end-begin).count();
  std::cout<<"t="<<time<<" ms"<<std::endl;
  return 0;
}

它在所有N<=1000000000的情况下都能正常工作,并给出以下输出:

n=1000000000

总数=500000000(0,500000000)=500000000

t=11297毫秒

但如果要使N大10倍,则输出是不正确的(第2行):

n=10000000000

总数=705032704(0,705 032704)=705032704

t=112660毫秒

这里的第二行必须是

总数=500000000(2,500000000)=705032706

我不能理解为什么程序在N=1e10时工作不正确,尽管N是长int。


共1个答案

匿名用户

检查中存在竞态条件:

if(n==INT_MAX)
{
    // Nothing prevents another thread to read n here and enter the same branch
    #pragma omp atomic update
    ++N;
    #pragma omp atomic write
    n=1;
}

因此,两个线程可能同时决定重置n和递增n

除此之外,您还必须为if检查本身逐个读取n,尽管这仅仅是不够的。

总体而言,最好使用实际的OpenMP约简或自定义构建的约简,以及支持实际原子操作的足够大的数据类型。