openMP并不是只能對循環來并行的,循環并行化單獨拿出來說是因為它在科學計算中非常有用,比如向量、矩陣的計算。所以我單獨拿出這一部分給大家講講。這里主要講解的是for循環。
編譯指導語句:
一般格式:
#pragma omp parallel for [clause[clause…]]
for(index = first; qualification; index_expr)
{…}
第一句中[]的部分是可選的,由自己的程序并行特點而定。大家先不要把精力放到這里面。后面的文章中會繼續講解的。
并行化for的編寫規則
1、index的值必須是整數,一個簡單的for形式:for(int i = start; i < end; i++){…} 。
2、start和end可以是任意的數值表達式,但是它在并行化的執行過程中值不能改變,也就是說在for并行化執行之前,編譯器必須事先知道你的程序執行多少次,因為編譯器要把這些計算分配到不同的線程中執行。
3、循環語句只能是單入口但出口的。這里只要你避免使用跳轉語句就行了。具體說就是不能使用goto、break、return。但是可以使用continue,因為它并不會減少循環次數。另外exit語句也是可以用的,因為它的能力太大,他一來,程序就結束了。
例子講解
例1、for循環并行
#include
#include "omp.h"
int main(int argc, char* argv[])
{
int i;
#pragma omp parallel for
for (i = 0; i < 12; i++)
{ printf("i = %d %d/n", i, omp_get_thread_num()); }
return 0;
}
例1的執行結果如圖1所示:
圖1、例1的執行結果
從結果中可以看出 i 屬于{0,1,2}時由0號線程執行,i 屬于{3,4,5}時由1號線程執行,i 屬于{6,7,8}時由2號線程執行,i 屬于{9,10,11}時由3號線程執行。omp_get_thread_num()這個函數通過執行結果大家也知道了,他返回每個線程的編號。
并行編譯子句
openMP中有多種并行化子句,這些子句都是為控制循環并行化編譯而設定的,這里我們主要關注數據作用域子句,這里的數據作用域是指各個線程是否對某一變量有權訪問。shared子句用來標記變量在各個線程之間是共享的,private子句標記變量在各個線程之間是私有的,實際上它會在在每個線程中保存一個副本。默認情況下,并行執行的變量是共享的。至于其它編譯子句將在后面的文章中介紹。
用實例講解數據作用域子句
實際上我很難想到一個綜合的例子來講解這種子句的限制異同,所以我寫了幾個例子。
例2、private
#include
#include "omp.h"
int main(int argc, char* argv[])
{
float x = 4.3f;
int i;
#pragma omp parallel for private(x)
for (i = 0; i < 12; i++)
{
x = 0;
printf("parallel x = %.1f, thread nummber:%d/n", x, omp_get_thread_num());
}
printf("/nserial x = %.1f, thread nummber:%d/n", x, omp_get_thread_num());
return 0;
}
圖2、例2執行結果
例3 firstprivate(var):指定var在每個線程中都有一個副本,并且var的初始值在并行執行開始之前定義,每個并行線程的var的副本初值就是串行時定義的初始值。程序結束后串行程序中的var值并不會改變。
#include
#include "omp.h"
int main(int argc, char* argv[])
{
float x = 4.3f;
int i;
#pragma omp parallel for firstprivate(x)
for (i = 0; i < 12; i++)
{
x += 1.0f;
printf("parallel x = %.1f, thread nummber:%d/n", x, omp_get_thread_num());
}
printf("/nserial x = %.1f, thread nummber:%d/n", x, omp_get_thread_num());
return 0;
}
圖3、例3的執行結果
例4 lastprivate(var):指定最后多線程執行完后原串行循環最后一次var的值帶到主線程(串行部分)
#include
#include "omp.h"
int main(int argc, char* argv[])
{
float x = 4.3f;
int i;
#pragma omp parallel for lastprivate(x)
for (i = 0; i < 12; i++)
{
x = 0.0f;
printf("parallel x = %.1f, thread nummber:%d/n", x, omp_get_thread_num());
}
printf("/nserial x = %.1f, thread nummber:%d/n", x, omp_get_thread_num());
return 0;
}
圖4、例4的執行結果
例5 firstprivate與lastprivate聯用,很奇怪openMP很多情況下是不允許某個變量被指定兩次規則的,他倆卻可以,呵呵,而且配合效果還不錯。
#include
#include "omp.h"
int main(int argc, char* argv[])
{
float x = 4.3f;
int i;
#pragma omp parallel for firstprivate(x) lastprivate(x)
for (i = 0; i < 12; i++)
{
x += (float)omp_get_thread_num();
printf("parallel x = %.1f, thread nummber:%d/n", x, omp_get_thread_num());
}
printf("/nserial x = %.1f, thread nummber:%d/n", x, omp_get_thread_num());
return 0;
}
圖5、例5的執行結果
從上面的例2、3的程序中可以看出例2中每個線程中x都是私有的,它屬于每個線程,在主線程的定義并不能帶入到各個線程中,使用firstprivate后,x在主線程的初始值可以帶到各個線程中,在圖3可以看出每個線程x的輸出結果實際是相同的,但是在并行執行結束后,主線程中的x值仍然為4.3。從例4的執行結果可以看出最后x的值帶出到了主線程中,這個x值到底是哪個線程中的哪?答案是最后一句x賦值后的值,哪個線程執行完的最晚就是哪個x的值。例5顯示firstprivate與lastprivate聯合使用的執行結果。
例6 reduction規約操作,
執行reduction的變量要特別注意,以reduction(+:sum)為例。
第一種情況:sum為局部變量。這是你必須為sum在串行程序中賦初值,sum 被設置為每個線程私有,先各自執行完算出各自的sum值,最后主線程會將 《線程數+1》個sum變量規約,比如你num_thread(4),在開始并行執行之前你對規約變量賦初值為10,并行時假設每個線程算的的sum值為1,那么最終sum帶到串行程序中的變量值為14(串行的10+四個線程的1)。
第二種情況:sum為全局變量。這是你不必為sum賦初始值,因為此時默認串行的sum值為0,進入每個線程的sum值也是0,規約時仍然是將《線程數+1》個sum值相加,因為你并沒有對全局的sum賦初值,所以最后規約的結果看著像是只有各線程的sum參加了規約操作。其實當你將全局的sum賦初值時,你會發現最后規約的sum值又多加了全局變量sum的串行程序結果。
重要提醒:不管你怎樣設計sum的串行聲明形式,只要他在被定義為規約變量,每次進入并行線程的sum值都是0;
也許你想把每個并行線程的sum值初始化成一個非0的值,然后再各自線程中在使用,那么我可以告訴你,別想了(至少我沒有做到)。因為我規約sum值,如果這個規約有意義你的每個線程應該是各自獨立未回各自的sum的,那么這個初始值使用0就已經非常好了,因為各自的sum計算如果結果一樣,你為何不直接用一句乘法哪(線程數*一個線程計算的sum值)。
#include
#include "omp.h"
int main(int argc, char* argv[])
{
float x = 0.0f;
int i;
float sum = 0.0f;
#pragma omp parallel for private(x) reduction(+:sum)
for (i = 0; i < 12; i++)
{
x = (float)omp_get_thread_num();
sum += x;
printf("parallel sum = %.1f, thread nummber:%d/n", sum, omp_get_thread_num());
}
printf("/nserial sum = %.1f, thread nummber:%d/n", sum, omp_get_thread_num());
return 0;
}
圖6、例6執行結果
在例6中我使用了reduction(+:sum),這表示每個線程對sum這個共享變量執行加操作時其它任何線程不能對它進行加操作,實際上我們這樣理解是有偏差的,真正的機理在執行結果中不難看出,實際每個線程都拷貝了一個sum的副本,先在自己執行時加完sum,等所有線程都執行結束后,主線程再將每個線程的sum副本的值加起來返回給主線程中sum。
小結
本節主要講述了for語句的并行化。現在為止大家應該可以熟練使用for并行化了。文章中可能還有些不全面的地方,熱切期望各位讀者能給出批評和指正,期待中……
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
