OpenMP基本概念
?
OpenMP是一種用于共享內存并行系統的多線程程序設計方案,支持的編程語言包括C、C++和Fortran。OpenMP提供了對并行算法的高層抽象描述,特別適合在多核CPU機器上的并行程序設計。編譯器根據程序中添加的pragma指令,自動將程序并行處理,使用OpenMP降低了并行編程的難度和復雜度。當編譯器不支持OpenMP時,程序會退化成普通(串行)程序。程序中已有的OpenMP指令不會影響程序的正常編譯運行。
?
在VS中啟用OpenMP很簡單,很多主流的編譯環境都內置了OpenMP。在項目上右鍵->屬性->配置屬性->C/C++->語言->OpenMP支持,選擇“是”即可。
?
OpenMP執行模式
?
OpenMP采用fork-join的執行模式。開始的時候只存在一個主線程,當需要進行并行計算的時候,派生出若干個分支線程來執行并行任務。當并行代碼執行完成之后,分支線程會合,并把控制流程交給單獨的主線程。
一個典型的fork-join執行模型的示意圖如下:
?
?
OpenMP編程模型以線程為基礎,通過編譯制導指令制導并行化,有三種編程要素可以實現并行化控制,他們分別是編譯制導、API函數集和環境變量。
?
編譯制導
?
編譯制導指令以#pragma omp 開始,后邊跟具體的功能指令,格式如:#pragma?omp 指令[子句[,子句]?…]。常用的功能指令如下:
- parallel:用在一個結構塊之前,表示這段代碼將被多個線程并行執行;??
for:用于for循環語句之前,表示將循環計算任務分配到多個線程中并行執行,以實現任務分擔,必須由編程人員自己保證每次循環之間無數據相關性;??
parallel?for:parallel和for指令的結合,也是用在for循環語句之前,表示for循環體的代碼將被多個線程并行執行,它同時具有并行域的產生和任務分擔兩個功能;??
sections:用在可被并行執行的代碼段之前,用于實現多個結構塊語句的任務分擔,可并行執行的代碼段各自用section指令標出(注意區分sections和section);??
parallel?sections:parallel和sections兩個語句的結合,類似于parallel?for;
single:用在并行域內,表示一段只被單個線程執行的代碼;
critical:用在一段代碼臨界區之前,保證每次只有一個OpenMP線程進入;
flush:保證各個OpenMP線程的數據影像的一致性;
barrier:用于并行域內代碼的線程同步,線程執行到barrier時要停下等待,直到所有線程都執行到barrier時才繼續往下執行;
atomic:用于指定一個數據操作需要原子性地完成;
master:用于指定一段代碼由主線程執行;
threadprivate:用于指定一個或多個變量是線程專用,后面會解釋線程專有和私有的區別。
?
相應的OpenMP子句為:?
private:指定一個或多個變量在每個線程中都有它自己的私有副本;
firstprivate:指定一個或多個變量在每個線程都有它自己的私有副本,并且私有變量要在進入并行域或任務分擔域時,繼承主線程中的同名變量的值作為初值;
lastprivate:是用來指定將線程中的一個或多個私有變量的值在并行處理結束后復制到主線程中的同名變量中,負責拷貝的線程是for或sections任務分擔中的最后一個線程;?
reduction:用來指定一個或多個變量是私有的,并且在并行處理結束后這些變量要執行指定的歸約運算,并將結果返回給主線程同名變量;
nowait:指出并發線程可以忽略其他制導指令暗含的路障同步;
num_threads:指定并行域內的線程的數目;?
schedule:指定for任務分擔中的任務分配調度類型;
shared:指定一個或多個變量為多個線程間的共享變量;
ordered:用來指定for任務分擔域內指定代碼段需要按照串行循環次序執行;
copyprivate:配合single指令,將指定線程的專有變量廣播到并行域內其他線程的同名變量中;
copyin:用來指定一個threadprivate類型的變量需要用主線程同名變量進行初始化;??
default:用來指定并行域內的變量的使用方式,缺省是shared。
?
API函數
?
除上述編譯制導指令之外,OpenMP還提供了一組API函數用于控制并發線程的某些行為,下面是一些常用的OpenMP API函數以及說明:?
?
環境變量
?
OpenMP中定義一些環境變量,可以通過這些環境變量控制OpenMP程序的行為,常用的環境變量:
- OMP_SCHEDULE:用于for循環并行化后的調度,它的值就是循環調度的類型;??
OMP_NUM_THREADS:用于設置并行域中的線程數;???
OMP_DYNAMIC:通過設定變量值,來確定是否允許動態設定并行域內的線程數;??
OMP_NESTED:指出是否可以并行嵌套。?
?
簡單示例之parallel使用
?
parallel制導指令用來創建并行域,后邊要跟一個大括號將要并行執行的代碼放在一起:
?
#include<iostream>
#include"omp.h"
using namespace std;
void main()
{
#pragma omp parallel
{
cout << "Test" << endl;
}
system("pause");
}
?
?
執行以上程序有如下輸出:
?
程序打印出了4個“Test”,說明parallel后的語句被4個線程分別執行了一次,4個是程序默認的線程數,還可以通過子句num_threads顯式控制創建的線程數:
?
#include<iostream>
#include"omp.h"
using namespace std;
void main()
{
#pragma omp parallel num_threads(6)
{
cout << "Test" << endl;
}
system("pause");
}
?
?
編譯運行有如下輸出:
?
程序中顯式定義了6個線程,所以parallel后的語句塊分別被執行了6次。第二行的空行是由于每個線程都是獨立運行的,在其中一個線程輸出字符“Test”之后還沒有來得及換行時,另一個線程直接輸出了字符“Test”。
?
簡單示例之parallel for使用
?
使用parallel制導指令只是產生了并行域,讓多個線程分別執行相同的任務,并沒有實際的使用價值。parallel for用于生成一個并行域,并將計算任務在多個線程之間分配,從而加快計算運行的速度。可以讓系統默認分配線程個數,也可以使用num_threads子句指定線程個數。
?
#include<iostream>
#include"omp.h"
using namespace std;
void main()
{
#pragma omp parallel for num_threads(6)
for (int i = 0; i < 12; i++)
{
printf("OpenMP Test, 線程編號為: %d\n", omp_get_thread_num());
}
system("pause");
}
?
?
運行輸出:
?
上邊程序指定了6個線程,迭代量為12,從輸出可以看到每個線程都分到了12/6=2次的迭代量。
?
OpenMP效率提升以及不同線程數效率對比
?
?
#include<iostream>
#include"omp.h"
using namespace std;
void test()
{
for (int i = 0; i < 80000; i++)
{
}
}
void main()
{
float startTime = omp_get_wtime();
//指定2個線程
#pragma omp parallel for num_threads(2)
for (int i = 0; i < 80000; i++)
{
test();
}
float endTime = omp_get_wtime();
printf("指定 2 個線程,執行時間: %f\n", endTime - startTime);
startTime = endTime;
//指定4個線程
#pragma omp parallel for num_threads(4)
for (int i = 0; i < 80000; i++)
{
test();
}
endTime = omp_get_wtime();
printf("指定 4 個線程,執行時間: %f\n", endTime - startTime);
startTime = endTime;
//指定8個線程
#pragma omp parallel for num_threads(8)
for (int i = 0; i < 80000; i++)
{
test();
}
endTime = omp_get_wtime();
printf("指定 8 個線程,執行時間: %f\n", endTime - startTime);
startTime = endTime;
//指定12個線程
#pragma omp parallel for num_threads(12)
for (int i = 0; i < 80000; i++)
{
test();
}
endTime = omp_get_wtime();
printf("指定 12 個線程,執行時間: %f\n", endTime - startTime);
startTime = endTime;
//不使用OpenMP
for (int i = 0; i < 80000; i++)
{
test();
}
endTime = omp_get_wtime();
printf("不使用OpenMP多線程,執行時間: %f\n", endTime - startTime);
startTime = endTime;
system("pause");
}
?
?
以上程序分別指定了2、4、8、12個線程和不使用OpenMP優化來執行一段垃圾程序,輸出如下:
?
可見,使用OpenMP優化后的程序執行時間是原來的1/4左右,并且并不是線程數使用越多效率越高,一般線程數達到4~8個的時候,不能簡單通過提高線程數來進一步提高效率。
總結
以上是生活随笔為你收集整理的OpenMP基本概念的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JAVA xml转dom_如何在Java
- 下一篇: keil4内嵌汇编_keil C中嵌入汇