![](/img/trans.png)
[英]How does OpenMP use the atomic instruction inside reduction clause?
[英]atomic operations on a variable which is in OpenMP reduction clause
我有以下一段代碼。 它的想法很簡單。 我的完整程序中有數十億個事件,我需要計算其中一些不使用long int類型的事件。 所以,我必須使用 2 個int數字HIT和COUNT而不是 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_out
和omp_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
問題源於每個線程都有自己的HIT
和COUNT
副本。 許多線程將以HIT
中的大值結束。 由於循環結束時的 OpenMP reduce 子句,這些被聚合,導致HIT
的多個“溢出”。
所示代碼的 OpenMP 實現的簡單修復是包括
COUNT += HIT / MAX;
HIT %= MAX;
循環結束后。
原子寫入指令是一條紅鯡魚。 它改變了循環的時間,導致更多的線程達到溢出限制。
從您的問題描述中,聽起來您的代碼中的實際HIT
是int
,而不是long int
。 這更難解決,因為無法使用上面的簡單除法計算多次溢出,因為您沒有精確計算所有內容。 您還應該考慮使用unsigned
而不是有符號的int
類型,因為這可以延遲溢出問題,並且在發生溢出時,可以避免有符號值溢出時出現的未定義行為。
可能的解決方案包括:
MAX
和COUNT
變量和受互斥鎖保護的代碼塊來執行兩個值的非原子增加。std::atomic
的單個MAX
和COUNT
變量以及fetch_add
(或可能的exchange
)來處理更新。 如果使用unsigned
類型,則可以讓MAX
翻轉為 0,並在翻轉發生時更新COUNT
。MAX
更改為較小的數字,這樣 ( nThreads * MAX
) 不會超過數字限制。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.