簡體   English   中英

對 OpenMP 縮減子句中的變量進行原子操作

[英]atomic operations on a variable which is in OpenMP reduction clause

我有以下一段代碼。 它的想法很簡單。 我的完整程序中有數十億個事件,我需要計算其中一些不使用long int類型的事件。 所以,我必須使用 2 個int數字HITCOUNT而不是 1 個int數字,因為會有 1 個int變量溢出(非常大的循環計數)。

#include <fstream>
#include <cstring>
#include <cmath>
#include <random>
#include <limits>
#include <chrono>

using namespace std;

int N=1000000000;
long int K=20*N;
int HIT=0;
int COUNT=0;
long int MAX=std::numeric_limits<int>::max();

int main(int argc, char **argv)
{
  auto begin=std::chrono::steady_clock::now();
  for(long int i=0; i<K; ++i)
  {
    ++HIT;
    if(HIT == MAX)
    {
      ++COUNT;
      HIT=0;
      cout<<"COUNT="<<COUNT<<endl;
    }
  }
  auto end=std::chrono::steady_clock::now();
  cout<<"HIT="<<HIT<<endl;
  cout<<"COUNT="<<COUNT<<endl;

  const long int Total = HIT+COUNT*MAX;
  cout<<"Total="<<Total<<" MAX="<<MAX<<endl;
  if(Total==K) cout<<"Total == K"<<endl;
  else         cout<<"Total != K"<<endl;
  auto elapsed_ms=std::chrono::duration_cast<std::chrono::milliseconds>(end-begin);
  std::cout<<"time="<<elapsed_ms.count()<<" ms"<<std::endl;
  return 0;
}

該代碼在 1 個線程中正常工作,並給出以下 output:

COUNT=1
COUNT=2
COUNT=3
COUNT=4
COUNT=5
COUNT=6
COUNT=7
COUNT=8
COUNT=9
HIT=672647177
COUNT=9
Total=20000000000 MAX=2147483647
Total == K
time=30971 ms

如果可能的話,我需要讓它使用 OpenMP 並行工作,而不是使用互斥鎖或與編譯器實現相關的一些函數。 但是當我將其修改為:

#pragma omp parallel for simd reduction(+:HIT,COUNT)
  for(long int i=0; i<K; ++i)

output 如下:

HIT=20000000000
COUNT=0
Total=20000000000 MAX=2147483647
Total == K
time=2771 ms

最后,當我將代碼修改為:

#pragma omp parallel for simd reduction(+:HIT,COUNT)
for(long int i=0; i<K; ++i)
{
  ++HIT;
  if(HIT == MAX)
  {
    ++COUNT;
  #pragma omp atomic write
    HIT=0;
    cout<<"COUNT="<<COUNT<<endl;
  }
}

output 是:

COUNT=1
COUNT=1
COUNT=1
COUNT=1
COUNT=1
COUNT=1
COUNT=1
COUNT=1
HIT=2820130824
COUNT=8
Total=20000000000 MAX=2147483647
Total == K
time=4232 ms

誰能給我解釋一下發生了什么以及為什么 output 如此不同?

我需要使用 OpenMP 讓代碼正確並行工作,那么如何正確執行呢?

#pragma omp atomic write

正確還是我應該寫

#pragma omp atomic update?

是否可以對 OpenMP縮減子句中已經存在的值編寫原子操作?

使用英特爾 C++ 2019 編譯器。

g++ 不允許使用simd

#pragma omp parallel for simd reduction(+:HIT,COUNT)

如果放棄simd ,則代碼使用 g++ 無法正常工作。

簡單的+歸約不適用於兩個不完全獨立相加的整數,但從 OpenMP 4.0 開始,您可以聲明自己的歸約。 您需要做的就是在class (或struct )中抽象出計數器的兩個部分,並定義一個對這些對象求和的 function。 在下面的示例中,使用了重載的復合賦值運算符 ( += ):

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

using namespace std;

const long int MAX = std::numeric_limits<int>::max();
const long int K = MAX + 20L;

class large_count {
   int count, hit;
public:
   large_count() : count(0), hit(0) {}

   // Prefix increment operator
   large_count& operator++() {
      hit++;
      if (hit == MAX) {
         hit = 0;
         count++;
      }
      return *this;
   }

   // Compound assignment operator
   large_count& operator+=(const large_count& other) {
      count += other.count;
      long int sum_hit = (long)hit + other.hit;
      if (sum_hit >= MAX) {
         count++;
         hit = sum_hit - MAX;
      }
      else
         hit = sum_hit;
      return *this;
   }

   long total() const { return hit + count * MAX; }
};

#pragma omp declare reduction (large_sum : large_count : omp_out += omp_in)

int main() {
   large_count cnt;
   double t = -omp_get_wtime();
   #pragma omp parallel for reduction(large_sum : cnt)
   for (long int i = 0; i < K; i++)
      ++cnt;
   t += omp_get_wtime();
   cout << (cnt.total() == K ? "YES" : "NO") << endl;
   cout << t << " s" << endl;
}

使用以下方式聲明自定義縮減:

#pragma omp declare reduction (large_sum : large_count : omp_out += omp_in)

聲明分為三部分:

  • large_sum - 這是自定義歸約操作的名稱
  • large_count - 這是減少操作的類型
  • omp_out += omp_in - 這是組合器表達式。 omp_outomp_in是 OpenMP 運行時提供的特殊偽變量。 它們都是large_count類型。 組合器表達式必須組合這兩個值並更新omp_out的值。

樣品 output:

$ g++ --version
g++ (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
...
$ g++ -std=c++11 -fopenmp -o cnt cnt.cc
$ OMP_NUM_THREADS=1 ./cnt
YES
9.39628 s
$ OMP_NUM_THREADS=3 ./cnt
YES
3.79765 s

問題源於每個線程都有自己的HITCOUNT副本。 許多線程將以HIT中的大值結束。 由於循環結束時的 OpenMP reduce 子句,這些被聚合,導致HIT的多個“溢出”。

所示代碼的 OpenMP 實現的簡單修復是包括

COUNT += HIT / MAX;
HIT %= MAX;

循環結束后。

原子寫入指令是一條紅鯡魚。 它改變了循環的時間,導致更多的線程達到溢出限制。

從您的問題描述中,聽起來您的代碼中的實際HITint ,而不是long int 這更難解決,因為無法使用上面的簡單除法計算多次溢出,因為您沒有精確計算所有內容。 您還應該考慮使用unsigned而不是有符號的int類型,因為這可以延遲溢出問題,並且在發生溢出時,可以避免有符號值溢出時出現的未定義行為。

可能的解決方案包括:

  1. 使用單個MAXCOUNT變量和受互斥鎖保護的代碼塊來執行兩個值的非原子增加。
  2. 使用聲明為std::atomic的單個MAXCOUNT變量以及fetch_add (或可能的exchange )來處理更新。 如果使用unsigned類型,則可以讓MAX翻轉為 0,並在翻轉發生時更新COUNT
  3. MAX更改為較小的數字,這樣 ( nThreads * MAX ) 不會超過數字限制。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM