實驗平臺:win7, VS2010html
1. 介紹ios
並行計算機能夠簡單分爲共享內存和分佈式內存,共享內存就是多個核心共享一個內存,目前的PC就是這類(不論是隻有一個多核CPU仍是能夠插多個CPU,它們都有多個核心和一個內存),通常的大型計算機結合分佈式內存和共享內存結構,即每一個計算節點內是共享內存,節點間是分佈式內存。想要在這些並行計算機上得到較好的性能,進行並行編程是必要條件。目前流行的並行程序設計方法是,分佈式內存結構上使用MPI,共享內存結構上使用Pthreads或OpenMP。咱們這裏關注的是共享內存並行計算機,由於編輯這篇文章的機器就屬於此類型(普通的臺式機)。和Pthreads相比OpenMP更簡單,對於關注算法、只要求對線程之間關係進行最基本控制(同步,互斥等)的咱們來講,OpenMP再適合不過了。程序員
本文對windows上Visual Studio開發環境下的OpenMP並行編程進行簡單的探討。本文參考了wikipedia關於OpenMP條目、OpenMP.org(有OpenMP Specification)、MSDM上關於OpenMP條目以及教材《MPI與OpenMP並行程序設計(C語言版)》:算法
注意,OpenMP目前最新版本爲4.0.0,而VS2010僅支持OpenMP2.0(2002年版本),因此本文所講的也是OpenMP2.0,本文注重使用OpenMP得到接近核心數的加速比,因此OpenMP2.0也足夠了。express
2. 第一個OpenMP程序編程
step 1: 新建控制檯程序windows
step 2: 項目屬性,全部配置下「配置屬性>>C/C++>>語言>>OpenMP支持」修改成是(/openmp),以下圖:多線程
step 3: 添加以下代碼:分佈式
1 #include<omp.h> 2 #include<iostream> 3 int main() 4 { 5 std::cout << "parallel begin:\n"; 6 #pragma omp parallel 7 { 8 std::cout << omp_get_thread_num(); 9 } 10 std::cout << "\n parallel end.\n"; 11 std::cin.get(); 12 return 0; 13 }
step 4: 運行結果以下圖:ide
能夠看到,個人計算機是8核的(嚴格說是8線程的),這是咱們實驗室的小型工做站(至多支持24核)。
3. 「第一個OpenMP程序」幕後,並行原理
OpenMP由Compiler Directives(編譯指導語句)、Run-time Library Functions(庫函數)組成,另外還有一些和OpenMP有關的Environment Variables(環境變量)、Data Types(數據類型)以及_OPENMP宏定義。之因此說OpenMP很是簡單,是由於,全部這些總共只有50個左右,OpenMP2.0 Specification僅有100餘頁。第2節的「第一個OpenMP程序」的第6行「#pragma omp parallel」即Compiler Directive,「#pragma omp parallel」下面的語句將被多個線程並行執行(也即被執行不止一遍),第8行的omp_get_thread_num()即Run-time Library Function,omp_get_thread_num()返回當前執行代碼所在線程編號。
共享內存計算機上並行程序的基本思路就是使用多線程,從而將可並行負載分配到多個物理計算核心,從而縮短執行時間(同時提升CPU利用率)。在共享內存的並行程序中,標準的並行模式爲fork/join式並行,這個基本模型以下圖示:
其中,主線程執行算法的順序部分,當遇到須要進行並行計算式,主線程派生出(建立或者喚醒)一些附加線程。在並行區域內,主線程和這些派生線程協同工做,在並行代碼結束時,派生的線程退出或者掛起,同時控制流回到單獨的主線程中,稱爲匯合。對應第2節的「第一個OpenMP程序」,第4行對應程序開始,4-5行對應串行部分,6-9行對應第一個並行塊(8個線程),10-13行對應串行部分,13行對應程序結束。
簡單來講,OpenMP程序就是在通常程序代碼中加入Compiler Directives,這些Compiler Directives指示編譯器其後的代碼應該如何處理(是多線程執行仍是同步什麼的)。因此說OpenMP須要編譯器的支持。上一小節的step 2即打開編譯器的OpenMP支持。和Pthreads不一樣,OpenMP下程序員只須要設計高層並行結構,建立及調度線程均由編譯器自動生成代碼完成。
4. Compiler Directives
4.1 通常格式
Compiler Directive的基本格式以下:
#pragma omp directive-name [clause[ [,] clause]...]
其中「[]」表示可選,每一個Compiler Directive做用於其後的語句(C++中「{}」括起來部分是一個複合語句)。
directive-name能夠爲:parallel, for, sections, single, atomic, barrier, critical, flush, master, ordered, threadprivate(共11個,只有前4個有可選的clause)。
clause(子句)至關因而Directive的修飾,定義一些Directive的參數什麼的。clause能夠爲:copyin(variable-list), copyprivate(variable-list), default(shared | none), firstprivate(variable-list), if(expression), lastprivate(variable-list), nowait, num_threads(num), ordered, private(variable-list), reduction(operation: variable-list), schedule(type[,size]), shared(variable-list)(共13個)。
例如「#pragma omp parallel」表示其後語句將被多個線程並行執行,線程個數由系統預設(通常等於邏輯處理器個數,例如i5 4核8線程CPU有8個邏輯處理器),能夠在該directive中加入可選的clauses,如「#pragma omp parallel num_threads(4)」仍舊錶示其後語句將被多個線程並行執行,可是線程個數爲4。
4.2 詳細解釋
本節的敘述順序同個人另外一篇博文:OpenMP編程總結表,讀者能夠對照閱讀,也能夠快速預覽OpenMP全部語法。
若是沒有特殊說明,程序均在Debug下編譯運行。
parallel
parallel表示其後語句將被多個線程並行執行,這已經知道了。「#pragma omp parallel」後面的語句(或者,語句塊)被稱爲parallel region。
能夠用if clause條件地進行並行化,用num_threads clause覆蓋默認線程數:
1 int a = 0; 2 #pragma omp parallel if(a) num_threads(6) 3 { 4 std::cout << omp_get_thread_num(); 5 }
int a = 7; #pragma omp parallel if(a) num_threads(6) { std::cout << omp_get_thread_num(); }
能夠看到多個線程的執行順序是不能保證的。
private, firstprivate, shared, default, reduction, copyin clauses留到threadprivate directive時說。
for
第2節的「第一個OpenMP程序」其實不符合咱們對並行程序的預期——咱們通常並非要對相同代碼在多個線程並行執行,而是,對一個計算量龐大的任務,對其進行劃分,讓多個線程分別執行計算任務的每一部分,從而達到縮短計算時間的目的。這裏的關鍵是,每一個線程執行的計算互不相同(操做的數據不一樣或者計算任務自己不一樣),多個線程協做完成全部計算。OpenMP for指示將C++ for循環的屢次迭代劃分給多個線程(劃分指,每一個線程執行的迭代互不重複,全部線程的迭代並起來正好是C++ for循環的全部迭代),這裏C++ for循環須要一些限制從而能在執行C++ for以前肯定循環次數,例如C++ for中不該含有break等。OpenMP for做用於其後的第一層C++ for循環。下面是一個例子:
1 const int size = 1000; 2 int data[size]; 3 #pragma omp parallel 4 { 5 #pragma omp for 6 for(int i=0; i<size; ++i) 7 data[i] = 123; 8 }
默認狀況下,上面的代碼中,程序執行到「#pragma omp parallel」處會派生出7和線程,加上主線程共8個線程(在個人機器上),C++ for的1000次迭代會被分紅連續的8段——0-124次迭代由0號線程計算,125-249次迭代由1號線程計算,以此類推。可能你已經猜到了,具體C++ for的各次迭代在線程間如何分配能夠由clause指示,它就是schedule(type[,size]),後面會具體說。
若是parallel region中只包含一個for directive做用的語句,上面代碼就是這種狀況,此時能夠將parallel和for「縮寫」爲parallel for,上面代碼等價於這樣:
1 const int size = 1000; 2 int data[size]; 3 #pragma omp parallel for 4 for(int i=0; i<size; ++i) 5 data[i] = 123;
正確使用for directive有兩個條件,第1是C++ for符合特定限制,不然編譯器將報告錯誤,第2是C++ for的各次迭代的執行順序不影響結果正確性,這是一個邏輯條件。例子以下:
1 #pragma omp parallel num_threads(6) 2 { 3 #pragma omp for 4 for(int i=0; i<1000000; ++i) 5 if(i>999) 6 break; 7 }
編譯器報錯以下:
error C3010: 「break」: 不容許跳出 OpenMP 結構化塊
schedule(type[,size])設置C++ for的屢次迭代如何在多個線程間劃分:
下面是幾個例子,能夠先忽略critical directive:
1 #pragma omp parallel num_threads(3) 2 { 3 #pragma omp for 4 for(int i=0; i<9; ++i){ 5 #pragma omp critical 6 std::cout << omp_get_thread_num() << i << " "; 7 } 8 }
上面輸出說明0號線程執行0-2迭代,1號執行3-5,2號執行6-9,至關於schedule(static, 3)。
1 #pragma omp parallel num_threads(3) 2 { 3 #pragma omp for schedule(static, 1) 4 for(int i=0; i<9; ++i){ 5 #pragma omp critical 6 std::cout << omp_get_thread_num() << i << " "; 7 } 8 }
1 #pragma omp parallel num_threads(3) 2 { 3 #pragma omp for schedule(dynamic, 2) 4 for(int i=0; i<9; ++i){ 5 #pragma omp critical 6 std::cout << omp_get_thread_num() << i << " "; 7 } 8 }
ordered clause配合ordered directive使用,請見ordered directive,nowait留到barrier directive時說,private, firstprivate, lastprivate, reduction留到threadprivate directive時說。
sections
若是說for directive用做數據並行,那麼sections directive用於任務並行,它指示後面的代碼塊包含將被多個線程並行執行的section塊。下面是一個例子:
1 #pragma omp parallel 2 { 3 #pragma omp sections 4 { 5 #pragma omp section 6 std::cout << omp_get_thread_num(); 7 #pragma omp section 8 std::cout << omp_get_thread_num(); 9 } 10 }
上面代碼中2個section塊將被2個線程並行執行,多個個section塊的第1個「#pragma omp section」能夠省略。這裏有些問題,執行這段代碼是總共會有多少個線程呢,「#pragma omp parallel」沒有clause,默認是8個線程(又說的在個人機器上),2個section是被哪2個線程執行是不肯定的,當section塊多於8個時,會有一個線程執行不止1個section塊。
一樣,上面代碼能夠「縮寫」爲parallel sections:
1 #pragma omp parallel sections 2 { 3 #pragma omp section 4 std::cout << omp_get_thread_num(); 5 #pragma omp section 6 std::cout << omp_get_thread_num(); 7 }
nowait clause留到barrier directive時說,private, firstprivate, lastprivate, reduction clauses留到threadprivate directive時說。
single
指示代碼將僅被一個線程執行,具體是哪一個線程不肯定,例子以下:
1 #pragma omp parallel num_threads(4) 2 { 3 #pragma omp single 4 std::cout << omp_get_thread_num(); 5 std::cout << "-"; 6 }
這裏0號線程執行了第4 5兩行代碼,其他三個線程執行了第5行代碼。
nowait clause留到barrier directive時說,private, firstprivate, copyprivate clauses留到threadprivate directive時說。
master
指示代碼將僅被主線程執行,功能相似於single directive,但single directive時具體是哪一個線程不肯定(有多是當時閒的那個)。
critical
定義一個臨界區,保證同一時刻只有一個線程訪問臨界區。觀察以下代碼及其結果:
1 #pragma omp parallel num_threads(6) 2 { 3 std::cout << omp_get_thread_num() << omp_get_thread_num(); 4 }
5號線程執行第3行代碼時被2號線程打斷了(並非每次運行均可能出現打斷)。
1 #pragma omp parallel num_threads(6) 2 { 3 #pragma omp critical 4 std::cout << omp_get_thread_num() << omp_get_thread_num(); 5 }
此次無論運行多少遍都不會出現某個數字不是連續兩個出現,由於在第4行代碼被一個線程執行期間,其餘線程不能執行(該行代碼是臨界區)。
barrier
定義一個同步,全部線程都執行到該行後,全部線程才繼續執行後面的代碼,請看例子:
1 #pragma omp parallel num_threads(6) 2 { 3 #pragma omp critical 4 std::cout << omp_get_thread_num() << " "; 5 #pragma omp critical 6 std::cout << omp_get_thread_num()+10 << " "; 7 }
1 #pragma omp parallel num_threads(6) 2 { 3 #pragma omp critical 4 std::cout << omp_get_thread_num() << " "; 5 #pragma omp barrier 6 #pragma omp critical 7 std::cout << omp_get_thread_num()+10 << " "; 8 }
能夠看到,這時一位數數字打印完了纔開始打印兩位數數字,由於,全部線程執行到第5行代碼時,都要等待全部線程都執行到第5行,這時全部線程再都繼續執行第7行及之後的代碼,即所謂同步。
再來講說for, sections, single directives的隱含barrier,以及nowait clause以下示例:
1 #pragma omp parallel num_threads(6) 2 { 3 #pragma omp for 4 for(int i=0; i<10; ++i){ 5 #pragma omp critical 6 std::cout << omp_get_thread_num() << " "; 7 } 8 // There is an implicit barrier here. 9 #pragma omp critical 10 std::cout << omp_get_thread_num()+10 << " "; 11 }
1 #pragma omp parallel num_threads(6) 2 { 3 #pragma omp for nowait 4 for(int i=0; i<10; ++i){ 5 #pragma omp critical 6 std::cout << omp_get_thread_num() << " "; 7 } 8 // The implicit barrier here is disabled by nowait. 9 #pragma omp critical 10 std::cout << omp_get_thread_num()+10 << " "; 11 }
sections, single directives是相似的。
atomic
atomic directive保證變量被原子的更新,即同一時刻只有一個線程再更新該變量(是否是很像critical directive),見例子:
1 int m=0; 2 #pragma omp parallel num_threads(6) 3 { 4 for(int i=0; i<1000000; ++i) 5 ++m; 6 } 7 std::cout << "value should be: " << 1000000*6 << std::endl; 8 std::cout << "value is: "<< m << std::endl;
m實際值比預期要小,由於「++m」的彙編代碼不止一條指令,假設三條:load, inc, mov(讀RAM到寄存器、加1,寫回RAM),有可能線程A執行到inc時,線程B執行了load(線程A inc後的值還沒寫回),接着線程A mov,線程B inc後再mov,本來應該加2就變成了加1。
使用atomic directive後能夠獲得正確結果:
1 int m=0; 2 #pragma omp parallel num_threads(6) 3 { 4 for(int i=0; i<1000000; ++i) 5 #pragma omp atomic 6 ++m; 7 } 8 std::cout << "value should be: " << 1000000*6 << std::endl; 9 std::cout << "value is: "<< m << std::endl;
那用critical directive行不行呢:
1 int m=0; 2 #pragma omp parallel num_threads(6) 3 { 4 for(int i=0; i<1000000; ++i) 5 #pragma omp critical 6 ++m; 7 } 8 std::cout << "value should be: " << 1000000*6 << std::endl; 9 std::cout << "value is: "<< m << std::endl;
差異爲什麼呢,顯然是效率啦,咱們作個定量分析:
1 #pragma omp parallel num_threads(6) 2 { 3 for(int i=0; i<1000000; ++i) ; 4 } 5 int m; 6 double t, t2; 7 m = 0; 8 t = omp_get_wtime(); 9 #pragma omp parallel num_threads(6) 10 { 11 for(int i=0; i<1000000; ++i) 12 ++m; 13 } 14 t2 = omp_get_wtime(); 15 std::cout << "value should be: " << 1000000*6 << std::endl; 16 std::cout << "value is: "<< m << std::endl; 17 std::cout << "time(S): " << t2-t << std::endl; 18 m = 0; 19 t = omp_get_wtime(); 20 #pragma omp parallel num_threads(6) 21 { 22 for(int i=0; i<1000000; ++i) 23 #pragma omp critical 24 ++m; 25 } 26 t2 = omp_get_wtime(); 27 std::cout << "value should be: " << 1000000*6 << std::endl; 28 std::cout << "value is: "<< m << std::endl; 29 std::cout << "time of critical(S): " << t2-t << std::endl; 30 m = 0; 31 t = omp_get_wtime(); 32 #pragma omp parallel num_threads(6) 33 { 34 for(int i=0; i<1000000; ++i) 35 #pragma omp atomic 36 ++m; 37 } 38 t2 = omp_get_wtime(); 39 std::cout << "value should be: " << 1000000*6 << std::endl; 40 std::cout << "value is: "<< m << std::endl; 41 std::cout << "time of atomic(S): " << t2-t << std::endl;
按照慣例,須要列出機器配置:Intel Xeon Processor E5-2637 v2 (4核8線程 15M Cache, 3.50 GHz),16GB RAM。上面代碼須要在Release下編譯運行以得到更爲真實的運行時間(實際部署的程序不多是Debug版本的),第一個parallel directive的用意是跳過潛在的建立線程的步驟,讓下面三個parallel directives有相同的環境,以增長可比性。從結果能夠看出,沒有atomic clause或critical clause時運行時間短了不少,可見正確性是用性能置換而來的。不出所料,「大材小用」的critical clause運行時間比atomic clause要長不少。
flush
指示全部線程對全部共享對象具備相同的內存視圖(view of memory),該directive指示將對變量的更新直接寫回內存(有時候給變量賦值可能只改變了寄存器,後來才才寫回內存,這是編譯器優化的結果)。這很差理解,看例子,爲了讓編譯器盡情的優化代碼,須要在Release下編譯運行以下代碼:
1 int data, flag=0; 2 #pragma omp parallel sections num_threads(2) shared(data, flag) 3 { 4 #pragma omp section // thread 0 5 { 6 #pragma omp critical 7 std::cout << "thread:" << omp_get_thread_num() << std::endl; 8 for(int i=0; i<10000; ++i) 9 ++data; 10 flag = 1; 11 } 12 #pragma omp section // thread 1 13 { 14 while(!flag) ; 15 #pragma omp critical 16 std::cout << "thread:" << omp_get_thread_num() << std::endl; 17 -- data; 18 std::cout << data << std::endl; 19 } 20 }
程序進入了死循環…… 咱們的初衷是,用flag來作手動同步,線程0修改data的值,修改好了置flag,線程1反覆測試flag檢查線程0有沒有修改完data,線程1接着再修改data並打印結果。這裏進入死循環的可能緣由是,線程1反覆測試的flag只是讀到寄存器中的值,由於線程1認爲,只有本身在訪問flag(甚至覺得只有本身這1個線程),在本身沒有修改內存以前不須要從新去讀flag的值到寄存器。用flush directive修改後:
1 int data=0, flag=0; 2 #pragma omp parallel sections num_threads(2) shared(data, flag) 3 { 4 #pragma omp section // thread 0 5 { 6 #pragma omp critical 7 std::cout << "thread:" << omp_get_thread_num() << std::endl; 8 for(int i=0; i<10000; ++i) 9 ++data; 10 #pragma omp flush(data) 11 flag = 1; 12 #pragma omp flush(flag) 13 } 14 #pragma omp section // thread 1 15 { 16 while(!flag){ 17 #pragma omp flush(flag) 18 } 19 #pragma omp critical 20 std::cout << "thread:" << omp_get_thread_num() << std::endl; 21 #pragma omp flush(data) 22 -- data; 23 std::cout << data << std::endl; 24 } 25 }
這回結果對了,解釋一下,第10行代碼告訴編譯器,確保data的新值已經寫回內存,第17行代碼說,從新從內存讀flag的值。
ordered
使用在有ordered clause的for directive(或parallel for)中,確保代碼將被按迭代次序執行(像串行程序同樣),例子:
1 #pragma omp parallel num_threads(8) 2 { 3 #pragma omp for ordered 4 for(int i=0; i<10; ++i){ 5 #pragma omp critical 6 std::cout << i << " "; 7 #pragma omp ordered 8 { 9 #pragma omp critical 10 std::cout << "-" << i << " "; 11 } 12 } 13 }
只看前面有"-"的數字,是否是按順序的,而沒有"-"的數字則沒有順序。值得強調的是for directive的ordered clause只是配合ordered directive使用,而不是讓迭代有序執行的意思,後者的代碼是這樣的:
1 #pragma omp for ordered 2 for(int i=0; i<10; ++i) 3 #pragma omp ordered{ 4 ; // all the C++ for code 5 }
threadprivate
將全局或靜態變量聲明爲線程私有的。爲理解線程共享和私有變量,看以下代碼:
1 int a; 2 std::cout << omp_get_thread_num() << ": " << &a << std::endl; 3 #pragma omp parallel num_threads(8) 4 { 5 int b; 6 #pragma omp critical 7 std::cout << omp_get_thread_num() << ": " << &a << " " << &b << std::endl; 8 }
記住第3-7行代碼要被8個線程執行8遍,變量a是線程之間共享的,變量b是每一個線程都有一個(在線程本身的棧空間)。
怎麼區分哪些變量是共享的,哪些是私有的呢。在parallel region內定義的變量(非堆分配)固然是私有的。沒有特別用clause指定的(上面代碼就是這樣),在parallel region前(parallel region後的不可見,這點和純C++相同)定義的變量是共享的,在堆(用new或malloc函數分配的)上分配的變量是共享的(即便是在多個線程中使用new或malloc,固然指向這塊堆內存的指針多是私有的),for directive做用的C++ for的循環變量無論在哪裏定義都是私有的。
好了,回到threadprivate directive,看例子:
1 #include<omp.h> 2 #include<iostream> 3 int a; 4 #pragma omp threadprivate(a) 5 int main() 6 { 7 std::cout << omp_get_thread_num() << ": " << &a << std::endl; 8 #pragma omp parallel num_threads(8) 9 { 10 int b; 11 #pragma omp critical 12 std::cout << omp_get_thread_num() << ": " << &a << " " << &b << std::endl; 13 } 14 std::cin.get(); 15 return 0; 16 }
下面是最後幾個沒有講的clauses:private, firstprivate, lastprivate, shared, default, reduction, copyin, copyprivate clauses,先看private clause:
1 int a = 0; 2 std::cout << omp_get_thread_num() << ": " << &a << std::endl; 3 #pragma omp parallel num_threads(8) private(a) 4 { 5 #pragma omp critical 6 std::cout << omp_get_thread_num() << ": *" << &a << " " << a << std::endl; 7 }
private clause將變量a由默認線程共享變爲線程私有的,每一個線程會調用默認構造函數生成一個變量a的副本(固然這裏int沒有構造函數)。
firstprivate clause和private clause的區別是,會用共享版本變量a來初始化。lastprivate clause在private基礎上,將執行最後一次迭代(for)或最後一個section塊(sections)的線程的私有副本拷貝到共享變量。shared clause和private clause相對,將變量聲明爲共享的。以下例子,其中的shared clause能夠省略:
1 int a=10, b=11, c=12, d=13; 2 std::cout << "abcd's values: " << a << " " << b << " " << c << " " << d << std::endl; 3 #pragma omp parallel for num_threads(8) \ 4 firstprivate(a) lastprivate(b) firstprivate(c) lastprivate(c) shared(d) 5 for(int i=0; i<8; ++i){ 6 #pragma omp critical 7 std::cout << "thread " << omp_get_thread_num() << " acd's values: " 8 << a << " " << c << " " << d << std::endl; 9 a = b = c = d = omp_get_thread_num(); 10 } 11 std::cout << "abcd's values: " << a << " " << b << " " << c << " " << d << std::endl;
每一個線程都對a,b,c,d的值進行了修改。由於d是共享的,因此每一個線程打印d前可能被其餘線程修改了。parallel region結束,a的共享版本不變,b,c因爲被lastprivate clause聲明瞭,因此執行最後一次迭代的那個線程用本身的私有b,c更新了共享版本的b,c,共享版本d的值取決於那個線程最後更新d。
default(shared|none):參數shared同於將全部變量用share clause定義,參數none指示對沒有用private, shared, reduction, firstprivate, lastprivate clause定義的變量報錯。
reduction clause用於歸約,以下是一個並行求和的例子:
1 int sum=0; 2 std::cout << omp_get_thread_num() << ":" << &sum << std::endl << std::endl; 3 #pragma omp parallel num_threads(8) reduction(+:sum) 4 { 5 #pragma omp critical 6 std::cout << omp_get_thread_num() << ":" << &sum << std::endl; 7 #pragma omp for 8 for(int i=1; i<=10000; ++i){ 9 sum += i; 10 } 11 } 12 std::cout << "sum's valuse: " << sum << std::endl;
能夠看到變量sum在parallel region中是線程私有的,每一個線程用本身的sum求一部分和,最後將全部線程的私有sum加起來賦值給共享版本的sum。除了「+」歸約,/, |, &&等均可以做爲歸約操做的算法。
copyin clause讓threadprivate聲明的變量的值和主線程的值相同,以下例子:
1 #include<omp.h> 2 #include<iostream> 3 int a; 4 #pragma omp threadprivate(a) 5 int main() 6 { 7 a = 99; 8 std::cout << omp_get_thread_num() << ": " << &a << std::endl << std::endl; 9 #pragma omp parallel num_threads(8) copyin(a) 10 { 11 #pragma omp critical 12 std::cout << omp_get_thread_num() << ": *" << &a << " " << a << std::endl; 13 } 14 std::cin.get(); 15 return 0; 16 }
若是第9行代碼修改成去掉copyin clause,結果以下:
copyprivate clause讓不一樣線程中的私有變量的值在全部線程中共享,例子:
1 int a = 0; 2 #pragma omp parallel num_threads(8) firstprivate(a) 3 { 4 #pragma omp single copyprivate(a) 5 a = omp_get_thread_num()+10; 6 #pragma omp critical 7 std::cout << omp_get_thread_num() << ": *" << &a << " " << a << std::endl; 8 }
能寫在copyprivate裏的變量必須是線程私有的,變量a符合這個條件,從上面結果能夠看出,single directive的代碼是被第4號線程執行的,雖然第4號線程賦值的a只是這個線程私有的,可是該新值將被廣播到其餘線程的a,這就形成了上面的結果。
若是去掉copyprivate clause,結果變爲:
此次single directive的代碼是被第0號線程執行的。
呼,終於說完了,未盡事宜,見另外一篇文章:OpenMP共享內存並行編程總結表。
6. 加速比
加速比即同一程序串行執行時間除以並行執行時間,即並行化以後比串行的性能提升倍數。理論上,加速比受這些因素影響:程序可並行部分佔比、線程數、負載是否均衡(能夠查查Amdahl定律),另外,因爲實際執行時並行程序可能存在的總線衝突,使得內存訪問稱爲瓶頸(還有Cache命中率的問題),實際加速比通常低於理論加速比。
爲了看看加速比隨線程數增長的變化狀況,編寫了以下代碼,須要在Release下編譯運行代碼:
1 #include<iostream> 2 #include<omp.h> 3 int main(int arc, char* arg[]) 4 { 5 const int size = 1000, times = 10000; 6 long long int data[size], dataValue=0; 7 for(int j=1; j<=times; ++j) 8 dataValue += j; 9 10 #pragma omp parallel num_threads(16) 11 for(int i=0; i<1000000; ++i) ; 12 13 bool wrong; double t, tsigle; 14 for(int m=1; m<=16; ++m){ 15 wrong = false; 16 t = omp_get_wtime(); 17 for(int n=0; n<100; ++n){ 18 #pragma omp parallel for num_threads(m) 19 for(int i=0; i<size; ++i){ 20 data[i] = 0; 21 for(int j=1; j<=times; ++j) 22 data[i] += j; 23 if(data[i] != dataValue) 24 wrong = true; 25 } 26 } 27 t = omp_get_wtime()-t; 28 if(m==1) tsigle=t; 29 std::cout << "num_threads(" << m << ") rumtime: " << t << " s.\n"; 30 std::cout << "wrong=" << wrong << "\tspeedup: " << tsigle/t << "\tefficiency: " << tsigle/t/m << "\n\n"; 31 } 32 33 std::cin.get(); 34 return 0; 35 }
能夠看到,因爲咱們的程序是在操做系統層面上運行,而非直接在硬件上運行,上面的測試結果出現了看似難以想象的結果——效率居然有時能大於1!最好的加速比出如今num_threads(8)時,爲7.4左右,已經很接近物理核心數8了,充分利用多核原來如此簡單。