在科學和工程應用中,許多程序都要在循環執行上花大量的時間,如Fortran中的do循環和C語言中的for循環,通過並行中的loop-level可以減少這些循環的運行時間。OpenMP提供了parallel for或parallel do指令來對循環結構進行並行處理,這個指令可以用於大部分的循環結構,它也是OpenMP中使用最多和最頻繁的指令。當然,程序員必須清楚哪些循環是可以進行並行的。
OpenMP中用於循環的指令結構爲:
C:
#pragma ompparallel for [clause [clause …]]
for循環結構
Fortran:
!$omp parallel do[clause [,] [clause …]]
do循環結構
end do
注:方括號[]表示可選項。
在parallel for或parallel do指令後,緊接着就必須是for循環(對於C語言)或do循環(對於Fortran語言),並形體就是for或do循環中的程序。只是在fortran中還可以添加!$omp end parallel do指令來表示循環並行結束。OpenMP還提供了用於控制並行執行的一些循環選項條件(clause),根據這些條件的類型可以分爲範圍條件、schedule條件、if條件、ordered條件和copyin條件等。
1、範圍條件
範圍條件主要有default(shared | none)、shared(list)、private(list)、firstprivate(list)、lastprivate(list)和reduction(operator:list)等。default條件用於設置並形體中涉及到的變量默認是shared共享的還是非共享的,default後的括號中只有值shared或none。shared條件表示其後list列出的那些變量在並形體中是共享的,意思就是說並形體外部和並形體內部都可以對該變量進行讀取和修改,它就像全局變量一樣。
private條件用於表示其後list列出的那些變量在並形體中是私有的。既然是私有的,那麼在並形體外部設置的該變量值無論爲多少,在並形體中都將無法使用。所有這些private變量在並形體中都必須初始化設置值,換句話說,這些private的變量在並形體中與在並形體外是完全隔離的、毫無關係的。在for循環中定義的循環變量默認就是private的。下面舉一個例子來說明private,如下代碼:
// File: PrivateTest.cpp : 定義控制檯應用程序的入口點。
#include "stdafx.h"
#include<omp.h>
#include<iostream>
using namespace std;
//private測試
int PrivateTest()
{
cout<<"private輸出:\n";
inti=0,j=10;
#pragmaomp parallel for private(j)
for(i=0;i<8;i++)
{
cout<<i<<":"<<j<<"\n";
j++;
cout<<i<<":"<<j<<"\n";
}
return0;
}
如果運行該程序,將會出現變量j沒有初始化的錯誤,錯誤提示如下圖所示:
奇怪的是爲什麼沒有提示i?因爲i已經初始化爲0,而並形體中還沒有爲j設置初始化值,如果將j=10添加到for循環中,程序就運行正常了。如下代碼(紅色表示修改的部位):
// File: PrivateTest.cpp : 定義控制檯應用程序的入口點。
#include "stdafx.h"
#include<omp.h>
#include<iostream>
using namespace std;
//private測試
int PrivateTest()
{
cout<<"private輸出:\n";
inti=0,j=10;
#pragmaomp parallel for private(j)
for(i=0;i<8;i++)
{
j=10;
cout<<i<<":"<<j<<"\n";
j++;
cout<<i<<":"<<j<<"\n";
}
return0;
}
firstprivate條件用於表示其後list列出的變量在並形體中私有的,但其在並形體中的初始化值爲並行之前設置的值。同樣以上面的代碼爲例:
// File: FirstPrivateTest.cpp : 定義控制檯應用程序的入口點。
#include "stdafx.h"
#include<omp.h>
#include<iostream>
using namespace std;
//FirstPrivateTest測試
int FirstPrivateTest()
{
cout<<"firstprivate輸出:\n";
inti=0,j=10;
#pragmaomp parallel for firstprivate(j)
for(i=0;i<8;i++)
{
cout<<i<<":"<<j<<"\n";
j++;
cout<<i<<":"<<j<<"\n";
}
return0;
}
該代碼與之前代碼沒有什麼太大區別,儘管在並形體中沒有對j進行初始化,但是它能正常正確的運行,其原因就是將變量j設置成了firstprivate。雖說在並形體中並沒有對j進行初始化,但是在並形體中j的初始值就是10。
lastprivate條件與firstprivate的含義類似,只是它設置的變量需要在並形體中初始化,但是其最後的結果可以在並形體外部使用。
// File: LastPrivateTest.cpp : 定義控制檯應用程序的入口點。
#include "stdafx.h"
#include<omp.h>
#include<iostream>
using namespace std;
//LastPrivateTest測試
int LastPrivateTest()
{
cout<<"lastprivate輸出:\n";
inti=0,j=10;
#pragmaomp parallel for lastprivate(j)
for(i=0;i<8;i++)
{
j=5;
cout<<i<<":"<<j<<"\n";
j++;
cout<<i<<":"<<j<<"\n";
}
cout<<"最後j的值爲:"<<j<<"\n";
return0;
}
最後輸出的j的值爲6,注意,j的值不是10,也不是11,也不是5+8=13,而是5+1=6。另外,如果設置變量爲lastprivate,它必須在並形體中初始化,否則會出錯。
reduction條件具有shared和private的特性,其後結構爲(operator: list),其中操作operator用於每次累積時list列出變量的操作。這些操作只針對並形體並行時,它不會影響並形體中該變量的值(包括初始值),但是並形體中該變量值將影響最後該變量在並形體外部的值。該變量將分爲兩部分,一部分屬於shared類型,一部分屬於private類型,最終該變量的值就是該變量初始值(在並行之前的值)與並形體中private類型值的operator運算,如果operator爲+,則爲這些之和。如下面的例子:
// File: ReductionTest.cpp : 定義控制檯應用程序的入口點。
#include "stdafx.h"
#include<omp.h>
#include<iostream>
using namespace std;
//ReductionTest測試
int ReductionTest()
{
cout<<"reduction輸出:\n";
inti=0,j=10;
#pragmaomp parallel for reduction(+:j)
for(i=0;i<8;i++)
{
j=2;
cout<<i<<":"<<j<<"\n";
j++;
cout<<i<<":"<<j<<"\n";
}
cout<<"最後j的值爲:"<<j<<"\n";
return0;
}
程序運行結果爲:
reduction輸出:
0:2
0:3
6:2
6:3
2:2
2:3
3:2
3:3
1:2
1:3
5:2
5:3
4:2
4:3
7:2
7:3
最後j的值爲:34
從上面的結果可知,變量j在並形體中是以private形式存在,而最後在並形體之間通過操作(+)來完成各個並形體中j的值與初始值(10)的計算,所以最後j的值爲:10+(3)+(3)+(3)+(3)+(3)+(3)+(3)+(3)=34。如果操作符是*,則最終j的值爲:10*(3)*(3)*(3)*(3)*(3)*(3)*(3)*(3)=65610。
reduction的操作符主要有:
操作符 |
數據類型 |
初始值 |
+ |
整型,浮點型 |
0 |
- |
整型,浮點型 |
0 |
* |
整型,浮點型 |
1 |
& |
整型 |
On |
| |
整型 |
0 |
^ |
整型 |
0 |
&& |
整型 |
1 |
|| |
整型 |
0 |
其中初始值是指在沒有爲該reduction變量在並形體中初始化時,它默認的值。
如果要列出多個變量,list中用逗號隔開相鄰的變量。如果有多種類型的要定義,即有shared又有private等等,相互之間用空格隔開即可。如下格式:
#pragma omp parallel for private(a,b,c) firstprivate(d,e)reduction(+:sum)相關程序源碼下載地址:
http://download.csdn.net/detail/xwebsite/3843187