openmp理解與實踐–常見問題解決
平行計算已經有些年沒有碰過了。以前做cfd時,利用網格的分塊考慮任務並行,所以主要利用mpi實現。
其實在cfd中mpi並行的確是比較適合,各個程序間維護自己的記憶體空間,利用有限的通訊來實現互動,這是一種能夠很自然理解的模型。
最近實現乙個撲克贏率計算的小工具,在6人情況下,5個對手,當給出公共牌時,如果採用列舉遍歷計算,
需要迴圈:
binomial(45,2)*binomial(43,2)*binomial(41,2)*binomial(39,2)*binomial(37,2)
這樣乙個實現串型程式可能需要計算好幾天,於是在想能否利用並行來進行加速。然而由於這個任務中的迴圈是強耦合在一起的,很難採用某種方式進行任務劃分,所以利用mpi來實現多個程序的平行計算可能並不是非常合適,於是想能不能利用其它方式來實現,於是又想起openmp來,儘管我以前沒有用過,但還有點映象,所以學習著來用。
顯然openmp對於一定程式設計基礎的人來說,似乎實現起來應該是很簡單的事情。
我也是這麼自認為算是能程式設計的,所以在csdn上收了幾個帖子來看,的確不是非常複雜的。
做個簡單例子,加上標頭檔案,加上執行緒並行編譯標識語句,編譯執行,的確ok了。
但搬到贏率計算程式上,麻煩就來了,計算結果跟序列的不一致。怎麼辦?
我還是根據當前的理解快點解決掉問題,再看多幾個帖子,看看別人的介紹和示例,然後再去嘗試,
也用各種簡單示例來實踐,搞了將近一天還是存在問題。我想還是不能太偷懶,有時真的勻速則不達,還是老老實實去openmp官網,進行系統學習一下吧,不然靠示例是很難解決問題的,而且有的示例的實現與其解釋是不對應的,有些概念的解釋也是含混不清的。顯然有些帖子是沒有深入的理解openmp,也沒有說到點上,這些就不談了。
我相信問題的關鍵在於自身對於openmp的理解,所以我在官網上下來幾個tutorial來看,的確在循序漸進的講解下,我應該是悟了。
不講openmp的深層次原理,我總結openmp的理解如下:
利用openmp並行是執行緒的並行(執行緒包含在程序內,乙個程序可以有多個程序。多個程序的並行可以把cpu的多個核用起來,多個執行緒的並行也可以
將cpu的多個核用起來)
openmp是共享記憶體的,也就是說沒有特別指定為執行緒私有的,所有的程式的東西都是共享的(而基於mpi的多程序並行,則是程序私有的,各個程序間不共享資料空間)。(這一點是解決問題的關鍵,特別是使用陣列遇到一些稀奇的事情時,比如增加一句輸出導致結果正確,而注釋掉後結果由出錯了。這個方面的錯誤大多與陣列記憶體使用相關,在c、c++中常見,fortran也有。)
從「#pragma omp parallel」 開始程式進入並行區域,並行區域如果用{}包圍,那麼並行的**就是包含在其中的內容,如果使用「#pragma omp parallel for」只針對for迴圈,那麼並行的區域僅包含僅跟該標記的for迴圈的內部**。在並行**中如果使用「#pragma omp single」標記其後的一行**或者用{}包圍的**,則表示這些**僅在乙個執行緒中執行,這個執行緒不一定是主線程。
巢狀的在內部的迴圈一般不會自動並行化,而會採用序列執行。出發使用nested命令,我沒有深入嘗試,如有需要可以去了解。
openmp還有很多更複雜的機制,但我暫時用不到了,所以也沒有深入去學習,有需要的可以去了解。
先看乙個簡單的序列**,我們主要是要對其中的三重迴圈進行並行優化,注意到最內層的迴圈與其他層是沒有關係的,而第二層的遍歷遍歷k是與第一層的遍歷變數j相關的。其計算輸出的sumc結果為55。
針對直接的需求問題,在學習基礎上給出了openmp的理解,並進行了**實踐,應該說解決了openmp的初步使用問題。#include #include #include #include int main(int argc, const char * ar**)
sumb+=k;
} sumc+=sumb; }
end = clock();
std::cout<<"sumc="《要進行並行化,最簡單的方式是採用「#pragma omp parallel for」標記,並使用規約來進行全域性變數的更新,比如:
注意其中的兩處修改:
#include
#pragma omp parallel for reduction(+:sumc)
具體的作用前面已經解釋過。
由於採用g++編譯,所以命令加上選項 -fopenmp,比如:
g++ test-p.cpp -o testpa5.exe -fopenmp -o3
在使用g++編譯情況下,這一並行的**比前面的序列**執行還要慢一些,如果使用編譯優化選項-o3,可能差異更明顯可見,所以有時候並行並非一定會帶來效率的提公升,要看具體的問題來處理。
#include #include #include #include #include int main(int argc, const char * ar**)
sumb+=k;
} sumc+=sumb;
//printf("sumc=%d thread id=%d %d\n",sumc,omp_get_thread_num(),j);
} end = clock();
std::cout<<"sumc="《第二種方式是不使用自動的並行,而自己來設計並行,如下面的**所示
注意其中的修改:
#define max_threads 4主動定義執行緒的總數
omp_set_num_threads(max_threads);
主動設定執行緒組,使並行區域根據這個設定執行,就是在未修改執行緒組前,後面的並行區域都按照最大執行緒數max_threads進行執行。
#pragma omp parallel private(j,tid)
設定並行區域,後面跟著**進入並行,並使用private指定了各執行緒需要維護副本的變數j,tid。其實更好的處理是不使用全域性的j和tid,而使用臨時的j和tid,即在並行區域**中定義這兩個變數。
tid = omp_get_thread_num()
各執行緒將其id賦值給tid,用於後面的並行任務分配。
for(j=tid;j<7;j+=max_threads)
設計分配各個執行緒執行的**,顯然因為這樣的設定,各個執行緒執行的任務就給定了。對於執行緒0,那麼執行的j迴圈包含j=0,j=4。
對於執行緒1,那麼執行的j迴圈包含j=1,j=5。
對於執行緒2,那麼執行的j迴圈包含j=2,j=6。
對於執行緒3,那麼執行的j迴圈包含j=3。
全域性更新的變數sumc,在不使用
#pragma omp critical
或#pragma omp atomic
時也沒有出錯,但最好還是用一下。
#include #include #include #include #include int main(int argc, const char * ar**)
sumb+=k;
}//printf("sumb=%d thread id=%d\n",sumb,omp_get_thread_num());
//#pragma omp critical
//#pragma omp atomic
sumc+=sumb;
} //printf("sumc=%d thread id=%d\n",sumc,omp_get_thread_num());
} end = clock();
std::cout<<"sumc="《下面再來看乙個例子,迴圈的建立不同數量的執行緒組進行平行計算:
其中建立了6個執行緒組進行比較,分別使用1/2/3/4/5/6個執行緒進行計算。
#include #include #include #include #include int main(int argc, const char * ar**)
sumb+=k;
}//printf("sumb=%d thread id=%d %d\n",sumb,omp_get_thread_num(),j1);
#pragma omp atomic
sumc+=sumb;
}#pragma omp single
printf("\n");
for ( k=0; k < 11; k++)
printf("\n");
#pragma omp parallel for
for ( k=0; k < 11; k++)
printf("\n");
for ( k=0; k < 11; k++)
printf("\n");
return 0;
}
因為沒有需求的原因,也沒有繼續深入下去應用更為複雜的功能。簡單其實也是我追求的,盡可能使用一些簡單的東西,對於後期的理解來說是更有好處的,因為很有可能用完這段之後很久都不會再用了,發出來下來當做記錄吧。
參考:hands-on introduction to openmp, mattson and meadows, from sc08 (austin)
其它一些csdn不少文章,也瀏覽過,沒有一一記錄下來,有興趣自行搜尋。
RabbitMQ 實踐經驗與常見問題
生產者傳送訊息到broker的可靠性 服務端確認機制 交換機的可靠性 路由保證 佇列的可靠性 消費端的可靠性 消費者確認機制 為什麼訊息會重複?消費端 響應 假如生產者未收到 重發 重發間隔 重發次數 如何冪等處理訊息 業務訊息必須有唯一的業務id,訊息id。即msgid bizid。資料庫中的唯一...
常見問題 朗動常見問題
常見問題一 方向盤變沉 檢查胎壓是否正常,輪胎是否過度磨損。助力幫浦不工作,前輪氣壓低。冬天的話,冷車在冬天助力油比較稠,方向會重一點。檢查轉向助力油。1 應該是是助力系統有問題或則助力潤滑油有問題。2 如果你在駕車時感覺方向盤變緊,汽車偏向一側,需要檢查輪胎,或進行車輪平衡 定位。在這些問題剛剛發...
kafka原理理解常見問題
1.kafka consumer是否可以消費指定分割槽訊息?kafka consumer消費訊息時,向broker發出 fetch 請求去消費特定分割槽的訊息,consumer指定訊息在日誌中的偏移量 offset 就可以消費從這個位置開始的訊息,customer擁有了offset的控制權,可以向後...