[英]Trouble with nested loops and openmp
我在將openmp應用於這樣的嵌套循環時遇到了麻煩:
#pragma omp parallel shared(S2,nthreads,chunk) private(a,b,tid)
{
tid = omp_get_thread_num();
if (tid == 0)
{
nthreads = omp_get_num_threads();
printf("\nNumber of threads = %d\n", nthreads);
}
#pragma omp for schedule(dynamic,chunk)
for(a=0;a<NREC;a++){
for(b=0;b<NLIG;b++){
S2=S2+cos(1+sin(atan(sin(sqrt(a*2+b*5)+cos(a)+sqrt(b)))));
}
} // end for a
} /* end of parallel section */
當我將序列號與openmp版本進行比較時,最后一個給出了奇怪的結果。 即使我刪除了#pragma omp,openmp的結果也不正確,您知道為什么還是可以指向一個關於雙循環和openmp的好的教程嗎?
這是種族狀況的經典例子。 每個openmp線程都在同時訪問和更新共享值,並且不保證某些更新不會丟失(充其量),或者所得到的答案不會變得亂七八糟(最壞的情況)。
比賽條件的問題在於,他們敏感地取決於時間。 在較小的情況下(例如,使用較小的NREC和NLIG),您有時可能會錯過這一點,但是在較大的情況下,它將最終總是出現。
沒有#pragma omp for
會得到錯誤答案的原因是,一旦您進入並行區域,所有的openmp線程就會啟動; 除非你使用類似的omp for
(所謂工作分享結構)分裂的工作,每個線程都做在並行部分的一切 -所以所有的線程會做同樣的全部資金,全部更新S2
simultatneously。
您必須小心OpenMP線程更新共享變量。 OpenMP具有atomic
操作,可讓您安全地修改共享變量。 下面是一個示例(不幸的是,您的示例對求和順序非常敏感,很難看到發生了什么,因此我對您的求和進行了一些更改:)。 在mysumallatomic
,每個線程都像以前一樣更新S2
,但是這次安全地完成了它:
#include <omp.h>
#include <math.h>
#include <stdio.h>
double mysumorig() {
double S2 = 0;
int a, b;
for(a=0;a<128;a++){
for(b=0;b<128;b++){
S2=S2+a*b;
}
}
return S2;
}
double mysumallatomic() {
double S2 = 0.;
#pragma omp parallel for shared(S2)
for(int a=0; a<128; a++){
for(int b=0; b<128;b++){
double myterm = (double)a*b;
#pragma omp atomic
S2 += myterm;
}
}
return S2;
}
double mysumonceatomic() {
double S2 = 0.;
#pragma omp parallel shared(S2)
{
double mysum = 0.;
#pragma omp for
for(int a=0; a<128; a++){
for(int b=0; b<128;b++){
mysum += (double)a*b;
}
}
#pragma omp atomic
S2 += mysum;
}
return S2;
}
int main() {
printf("(Serial) S2 = %f\n", mysumorig());
printf("(All Atomic) S2 = %f\n", mysumallatomic());
printf("(Atomic Once) S2 = %f\n", mysumonceatomic());
return 0;
}
但是,原子操作確實會損害並行性能(畢竟,要點是防止在變量S2
周圍進行並行操作!),因此更好的方法是進行求和,並且僅在兩次求和之后才執行原子操作,而不是執行128。 * 128次; 那是mysumonceatomic()
例程,該例程僅產生每個線程一次同步開銷,而不是每個線程16,000次。
但這是一種常見的操作,無需您自己實施。 可以使用OpenMP內置功能進行約簡操作(約簡是一種操作,如計算列表總和,查找列表的最小值或最大值等,僅通過查看即可一次完成一個元素到目前為止的結果以及下一個元素),如@ejd所建議。 OpenMP將運行並且速度更快(它的優化實現比其他OpenMP操作自己可以完成的速度要快得多)。
如您所見,這兩種方法都有效:
$ ./foo
(Serial) S2 = 66064384.000000
(All Atomic) S2 = 66064384.000000
(Atomic Once) S2 = 66064384.00000
問題不在於雙循環,而在於變量S2。 嘗試在您的for指令上放置一個減少條款:
#pragma omp用於排定(動態,塊)減少(+:S2)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.