openmp教程
OpenMP簡介
OpenMP程式設計總結表
- cpu核數擴充性問題:建立的線程數量需要随cpu核數變化,不能建立固定數量的線程
- 友善性問題:負載要均衡
- 可移植問題:不存在可移植問題,openmp為标準規範
OpenMP是由一組計算機硬體和軟體供應商聯合定義的應用程式接口(API);
OpenMP為基于共享記憶體的并行程式的開發人員提供了一種便攜式和可擴充的程式設計模型,其API支援各種架構上的C/C++和Fortran;
openmp=編譯器指令+庫函數+環境變量;
OpenMP程式設計模型
共享記憶體模型
分叉-合并模型
- OpenMP采用分叉-合并模型(fork-join)實作并行化
- 所有的OpenMP程式都從一個 主線程 開始。主線程串行執行,直到遇到第一個并行區域
- 分叉:之後主線程将建立一組并行線程。
- 并行區域内的代碼被用大括号包圍起來,然後在多個并行線程上被并行執行。
- 合并:當并行線程執行完成并行區域内的代碼之後,它們進行同步并且自動結束,隻剩下主線程。
輸入輸出 (I/O)
- OpenMP沒有對并行I/O做出規定,是以對于多個線程嘗試讀寫同一個檔案的情況要特别小心;
- 但如果每個線程對不同的檔案進行I/O操作,則問題并不重要;
- 程式設計者有完全責任確定I/O在多線程中被正确地執行
動态線程
- API也提供了運作時環境,來動态第更改用于執行并行區域的線程數,在有可能的情況下盡可能地有效利用已有資源
- 軟硬體實作中可能支援,也可能不支援此功能
OpenMP API總覽
- 編譯器指令(44個)
- 運作時庫函數(35個)
- 環境變量(13條個)
編譯器指令
編譯器指令在你的源代碼中可能被顯示為注釋,并且被編譯器所忽略,除非你明顯地告訴編譯器
OpenMP的編譯器指令的目标主要有:
- 産生一個并行區域
- 劃分線程中的代碼塊
- 線上程之間配置設定循環疊代
- 序列化代碼段
- 同步線程間的工作
編譯器指令 | 作用 |
---|---|
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 | 用于指定一個或多個變量是線程專用,後面會解釋線程專有和私有的差別 |
例如: #pragma omp parallel default(shared) private(beta,pi)
運作時庫函數
Openmp庫函數是不斷增長的,目的是:
- 設定和查詢線程數
- 查詢線程的唯一辨別符(ID),線程的祖先辨別符,或者線程組的大小等
- 設定和查詢動态線程的屬性
- 查詢是否在并行區域,以及在什麼級别的并行區域中
- 設定和查詢嵌套并行
- 設定、初始化以及終止鎖或者嵌套鎖
- 查詢挂鐘時間和分辨率
庫函數 | 目标 |
---|---|
OMP_SET_NUM_THREADS | 設定在下一個并行區域中使用的線程數 |
OMP_GET_NUM_THREADS | 傳回目前處于執行調用的并行區域中的線程數 |
OMP_GET_MAX_THREADS | 傳回調用OMP_GET_NUM_THREADS函數可以傳回的最大值 |
OMP_GET_THREAD_NUM | 傳回組内線程的線程号(譯注:不要和線程總數搞混) |
OMP_GET_THREAD_LIMIT | 傳回可用于程式的最大OpenMP線程數 |
OMP_GET_NUM_PROCS | 傳回程式可用的處理器數 |
OMP_IN_PARALLEL | 用于确定正在執行的代碼是否是并行的 |
OMP_SET_DYNAMIC | 啟動或者禁用可執行并行區域的線程數(由運作時系統)的動态調整 |
OMP_GET_DYNAMIC | 用于确定是否啟動了動态線程調整 |
OMP_SET_NESTED | 用于啟用或者禁用嵌套并行 |
OMP_GET_NESTED | 用于确定嵌套并行是否被棄用 |
OMP_SET_SCHEDULE | 當“運作時”被用作OpenMP指令中的排程類型時,設定循環排程政策 |
OMP_GET_SCHEDULE | 當“運作時”被用作OpenMP指令中的排程類型時,傳回循環排程政策 |
OMP_SET_MAX_ACTIVE_LEVELS | 設定嵌套并行區域的最大數量 |
OMP_GET_MAX_ACTIVE_LEVELS | 傳回嵌套并行區域的最大數量 |
OMP_GET_LEVEL | 傳回嵌套并行區域的目前級别 |
OMP_GET_ANCESTOR_THREAD_NUM | 給定目前線程的嵌套級别,傳回其祖先線程的線程号 |
OMP_GET_TEAM_SIZE | 給定目前線程的嵌套級别,傳回其線程組的大小 |
OMP_GET_ACTIVE_LEVEL | 傳回包含調用任務的的嵌套活動并行區域的數量 |
OMP_IN_FINAL | 如果在最終任務區域中執行該例程,則傳回true;否則傳回false |
OMP_INIT_LOCK | 初始化與鎖變量相關聯的鎖 |
OMP_DESTROY_LOCK | 解除給定的鎖變量與所有鎖的關聯 |
OMP_SET_LOCK | 擷取鎖的所有權 |
OMP_UNSET_LOCK | 釋放鎖 |
OMP_TEST_LOCK | 嘗試設定鎖,但是如果鎖不可用,則不會阻止 |
OMP_INIT_NEST_LOCK | 初始化與鎖定變量關聯的嵌套鎖 |
OMP_DESTROY_NEST_LOCK | 将給定的嵌套鎖變量與所有鎖解除關聯 |
OMP_SET_NEST_LOCK | 擷取嵌套鎖的所有權 |
OMP_UNSET_NEST_LOCK | 釋放嵌套鎖 |
OMP_TEST_NEST_LOCK | 嘗試設定嵌套鎖,但如果鎖不可用,則不會阻止 |
OMP_GET_WTIME | 提供便攜式挂鐘計時程式 |
OMP_GET_WTICK | 傳回連續時鐘之間的秒數(雙精度浮點值) |
環境變量
OpenMP提供了一些環境變量,用來在運作時對并行代碼的執行進行控制
- 設定線程數
- 指定循環如何劃分
- 将線程綁定到處理器
- 啟用/禁用嵌套并行,設定最大的嵌套并行級别
- 啟用/禁用動态線程
- 設定線程堆棧大小
- 設定線程等待政策
環境變量 | 作用 |
---|---|
OMP_SCHEDULE | 僅僅适用于for, parallel for指令在排程從句被設定為RUNTIME的情況。該變量的值确定了處理器中的循環疊代如何被排程 |
OMP_NUM_THREADS | 設定在運作中可用的最大線程數 |
OMP_DYNAMIC | 啟用或者禁用在執行并行區域時可用線程數的動态調整。其合法的值為TRUE或者FALSE |
OMP_PROC_BIND | 啟用或者禁用與處理器綁定的線程,有效值為TRUE或者FALSE |
OMP_NESTED | 啟用或者禁用嵌套并行,其有效值為TRUE或者FALSE |
OMP_STACKSIZE | 用于控制所建立的線程(非主線程)的棧空間大小 |
OMP_WAIT_POLICY | |
OMP_MAX_ACTIVE_LEVELS | |
OMP_THREAD_LIMIT |
例如: export OMP_NUM_THREADS=8
OpenMP程式編譯
編譯器 | 指令 | openmp辨別 |
---|---|---|
icc | icc/icpc | -qopenmp |
gcc | gcc/g++ | -fopenmp |
常用Openmp指令
parallel
parallel語句後面要跟一個大括号對将要并行執行的代碼括起來;
parallel塊中每行代碼都被多個線程重複執行;
for
将一個for循環配置設定到多個線程中執行,不過要和parallel結合起來才會有效果;
parallel for
for循環體的代碼将被多個線程并行執行,它同時具有并行域的産生和任務分擔兩個功能;
一個parallel塊中可以有多個for:
#pragma omp parallel
{
#pragma omp for //塊1
do something;
#pragma omp for
do something; //塊2
}
線程會被均勻的配置設定到不同的for快上去執行;
section和sections
- section語句是用在sections語句裡用來将sections語句裡的代碼劃分成幾個不同的段,每段由一個線程執行,各線程工作量總和等于原來的工作量;
- 需要注意的是,這種方式需要保證各個section裡的代碼執行時間相差不多,否則某個section執行時間比其他section過長就達不到并行執行的效果了;
#pragma omp [parallel] sections [子句]
{
#pragma omp section
{
代碼塊
}
#pragma omp section
{
代碼塊
}
}
private
- 用于将一個或多個變量聲明成線程私有的變量
- 變量聲明成私有變量後,指定每個線程都有它自己的變量私有副本,其他線程無法通路私有副本
- 循環疊代變量在循環構造區域裡是私有的
- 聲明在循環構造區域内的自動變量都是私有的
- shared來修飾循環疊代變量,也不會改變循環疊代變量在循環構造區域中是私有的這一特點
firstprivate:繼承并行區域外的變量值
lastprivate:将并行區域内變量的值傳遞出去
shared: 線程無關變量都是shared
threadprivate:定義全局私有變量;
threadprivate和private的差別在于threadprivate聲明的變量通常是全局範圍内有效的,而private聲明的變量隻在它所屬的并行構造中有效;
#include<stdio.h>
#include<stdlib.h>
#include<omp.h>
int g=100;
#pragma omp threadprivate(g)
int main(int argc, char *argv[]){
int i,j;
int num_core=omp_get_num_procs();
printf("machine core =========== %d\n",num_core);
printf("%d-----\n",g); //g=100
#pragma omp parallel num_threads(4)
{
g=omp_get_thread_num();
printf("threadID: %d\n",g);
}
printf("%d-----\n",g); //g=0
#pragma omp parallel num_threads(6)
{
printf("g=%d,threadID: %d\n",g,omp_get_thread_num());
}
printf("%d-----\n",g); //g=0
return 0;
}
threadprivade總結:
- 作用于全局變量,變量應定義在main函數外面
int g=100;
#pragma omp threadprivate(g)
- 各個線程對threadprivade變量有自己的備份,互不影響,即使線程被銷毀,重新開辟時各線程值依舊不變
shared
- shared子句可以用于聲明一個或多個變量為共享變量
- 共享變量,是值在一個并行區域的線程組内的所有線程隻擁有變量的一個記憶體位址,所有線程通路同一位址
default
- default指定并行區域内變量的預設屬性
- default(shared):表示并行區域内的共享變量在不指定的情況下都是shared屬性
- default(none):表示必須顯式指定所有共享變量的資料屬性,否則會報錯,除非變量有明确的屬性定義(比如循環并行區域的循環疊代變量隻能是私有的)如果一個并行區域,沒有使用default子句,那麼其預設行為為default(shared)
reduction
- 用來對一個或多個變量進行歸約操作
- 每個線程将建立reduction變量的一個私有拷貝(變成私有的)
- 在并行區域的結束處,各個線程将私有拷貝的值進行歸約