2015-10-06 49 views
0

當我讀取還原變量的初始值是根據用於還原的運算符設置後,我決定不是記住這些默認值,而是顯式初始化它。所以我修改了代碼中question by Totonga如下OpenMP:無法更改還原變量的值

const int num_steps = 100000; 
double x, sum, dx = 1./num_steps; 

#pragma omp parallel private(x) reduction(+:sum) 
{ 
    sum = 0.; 
    #pragma omp for schedule(static) 
    for (int i=0; i<num_steps; ++i) 
    { 
    x = (i+0.5)*dx; 
    sum += 4./(1.+x*x); 
    } 
} 

但事實證明,無論是否我寫總和= 0或總和= 123.456碼產生相同的結果(用gcc-4.5.2編譯器)。請有人解釋我爲什麼? (如果可能,參考openmp標準)提前感謝大家。

P.S.因爲有些人反對初始化還原變量,所以我認爲稍微擴展一個問題是有意義的。如預期下面作品代碼:I初始化減小變量和得到的結果,這不依賴於MY初始值

int sum; 
#pragma omp parallel reduction(+:sum) 
{ 
    sum = 1; 
} 
printf("Reduction sum = %d\n",sum); 

打印結果將是我有核的數量,並且不爲0。

PPS再次更新我的問題。用戶Gilles給出了一個有見地的評論:並且在並行區域退出時,在進入該部分之前,將使用+運算符和變量的初始值來減少這些局部值。

好了,下面的代碼給我的結果3.142592653598146,這是不好計算pi,而不是預期103.141592653598146(初始密碼是給我的pi=3.141592653598146卓越的價值)

const int num_steps = 100000; 
double x, sum, dx = 1./num_steps; 

sum = 100.; 
#pragma omp parallel private(x) reduction(+:sum) 
{ 
    #pragma omp for schedule(static) 
    for (int i=0; i<num_steps; ++i) 
    { 
    x = (i+0.5)*dx; 
    sum += 4./(1.+x*x); 
    } 
} 
+0

我想你正在尋找最新OpenMP規範的第2.14.3.6條。只要你需要關心,每個線程都會得到一個被稱爲「sum」的私有變量,它被初始化爲「+」,爲0。你嘗試初始化'sum'到其他值是徒勞的。 –

+1

如果您很難記住OpenMP初始化還原變量的值,請記住只有一個*還原操作*的標識元素。 –

+0

我不明白爲什麼你不喜歡這麼多,我試圖初始化一個變量,並且同時對於我以後在這個變量上做的操作也沒問題。對於程序員來說,這是非常正常的行爲 - 初始化然後操作變量。還原條款向我保證,當我退出平行部分時,它會將所有私人副本收集到一個副本中,但我不明白在哪一點上可以改變它的價值。 –

回答

4

你爲什麼要這麼做那?這只是乞求你所有的靈魂的煩惱。我們定義了reduction子句以及初始化使用的局部變量的方式是有原因的,其理由是您不必僅僅因爲它們已經正確就記住這些初始化值。

但是,在您的代碼中,行爲是未定義的。讓我們來看看爲什麼...

假設您最初的代碼是這樣的:

const int num_steps = 100000; 
double x, sum, dx = 1./num_steps; 

sum = 0.; 
for (int i=0; i<num_steps; ++i) { 
    x = (i+0.5)*dx; 
    sum += 4./(1.+x*x); 
} 

好,使用OpenMP parallelising這將是 「正常」 的方式:

const int num_steps = 100000; 
double x, sum, dx = 1./num_steps; 

sum = 0.; 
#pragma omp parallel for reduction(+:sum) private(x) 
for (int i=0; i<num_steps; ++i) { 
    x = (i+0.5)*dx; 
    sum += 4./(1.+x*x); 
} 

很簡單,不是嗎?

現在,而不是當,你這樣做:

const int num_steps = 100000; 
double x, sum, dx = 1./num_steps; 

#pragma omp parallel private(x) reduction(+:sum) 
{ 
    sum = 0.; 
    #pragma omp for schedule(static) 
    for (int i=0; i<num_steps; ++i) 
    { 
    x = (i+0.5)*dx; 
    sum += 4./(1.+x*x); 
    } 
} 

你有一個問題...的原因是,在進入parallel區域,sum尚未初始化。所以當你聲明omp parallel reduction(+:sum)時,你創建了一個每線程專用版本sum,初始化爲對應於你的運算符reduction子句的「邏輯」初始值,這裏是0,因爲你要求+減少。並且在退出parallel區域時,使用+運算符和初始值這些變量的這些本地值將在輸入該節之前被減少。見this for reference

還原子句指定還原標識符和一個或多個 列表項。對於每個列表項,將在每個隱式任務或SIMD通道中創建一個私有副本,並使用還原標識符的初始化程序值 進行初始化。該區域結束後, 原列表項使用與還原標識符

因此,在總結相關組合的傳抄 的值更新,在退出時你有sum += sum_local_0 + sum_local_1 + ... sum_local_nbthreadsMinusOne

相當於

因此,因爲在你的代碼,sum沒有任何初始值,其於parallel地區的出口值沒有被定義爲好,可不管......

現在讓我們想象一下你做的確初始化它......然後,如果不是usi在parallel區域內的正確的初始化程序(如上面的代碼中的sum=0.;),您使用sum=1.;代替原因,那麼最終總和不會僅增加1,而會增加1倍使用的線程數parallel區域內,因爲額外的值將被計入多少個線程的次數。所以最後,只要使用reduction條款和變量的「預期」/「天真」的方式,這將讓你和後來的人們維護你的代碼很多麻煩。


編輯:它看起來像我的觀點是不夠清楚,所以我會試着更好地解釋它:

驗證碼:

int sum; 
#pragma omp parallel reduction(+:sum) 
{ 
    sum = 1; 
} 
printf("Reduction sum = %d\n",sum); 

有一個未定義的行爲因爲它相當於:

int sum, numthreads; 
#pragma omp parallel 
#pragma omp single 
numthreads = omp_get_num_threads(); 

sum += numthreads; // value of sum is undefined since it never was initialised 
printf("Reduction sum = %d\n",sum); 

現在,這段代碼是有效的:

int sum = 0; //here, sum has been initialised 
#pragma omp parallel reduction(+:sum) 
{ 
    sum = 1; 
} 
printf("Reduction sum = %d\n",sum); 

說服自己,剛剛看了我給標準的片段:

區域結束後, 原列表項與傳抄的值更新 使用與縮減標識符相關聯的組合器

因此,縮減使用私有縮減變量es 和原始值在退出時執行最終減少。所以如果原始值沒有設置,最終值也是未定義的。這不是因爲某種原因,你的編譯器給你一個看起來合適的值,代碼是正確的。

現在更清楚了嗎?

+2

當然*你爲什麼要這樣做?* –

+0

我認爲這將是說明性和簡單的,如果你已經表明如何通過在並行區域內設置'double sum_local = 0;清除它是私人的,並且'sum'是共享的。 –

+0

Deat Gilles,這是一個非常好的答案,但是我不明白,或者你不明白這個問題。你的觀點很簡單 - 我不允許初始化還原變量,這就是編譯器忽略我的初始化的原因。但是接下來的問題是:我可以在什麼時候操縱這個變量呢?我已經給這個問題添加了一段代碼,它顯示了還原變量的初始化以不同的方式工作。我想你的回答並不能解釋2種行爲的差異。 –