2015年3月24日 星期二

OpenMP


官網 http://openmp.org/

以下節錄自 http://zh.wikipedia.org/wiki/OpenMP


OpenMP(Open Multi-Processing)是一套支持跨平台共享內存方式的多執行緒並發的編程API,...,可以在大多數的處理器體系和作業系統中運行,包括Solaris, AIX, HP-UX, GNU/Linux, Mac OS X, 和Microsoft Windows。包括一套編譯器指令、庫和一些能夠影響運行行為的環境變量。

OpenMP是由OpenMP Architecture Review Board牽頭提

出的,並已被廣泛接受的,用於共享內存並行系統的多執行緒程序設計的一套指導性注釋(Compiler Directive)。...。OpenMP提供了對並行算法的高層的抽象描述,程式設計師通過在原始碼中加入專用的pragma來指明自己的意圖,由此編譯器可以自動將程序進行並行化,並在必要之處加入同步互斥以及通信。當選擇忽略這些pragma,或者編譯器不支持OpenMP時,程序又可退化為通常的程序(一般為串行),代碼仍然可以正常運作,只是不能利用多執行緒來加速程序執行。

OpenMP支持的程式語言包括C語言C++Fortran

介紹

OpenMP是一個跨平台的多執行緒實現,主執行緒(順序的執行指令)生成一系列的子執行緒,並將任務劃分給這些子執行緒進行執行。這些子執行緒並行的運行,由運行時環境將執行緒分配給不同的處理器。

要進行並行執行的代碼片段需要進行相應的標記,用預編譯指令使得在代碼片段被執行前生成執行緒,每個執行緒會分配一個id,可以通過函數(called omp_get_thread_num())來獲得該值,該值是一個整數,主執行緒的id為0。在並行化的代碼運行結束後,子執行緒join到主執行緒中,並繼續執行程序。

默認情況下,各個執行緒獨立的執行並行區域的代碼。可以使用Work-sharing constructs來劃分任務,使每個執行緒執行其分配部分的代碼。通過這種方式,使用OpenMP可以實現任務並行數據並行

運行時環境分配給每個處理器的執行緒數取決於使用方法、機器負載和其他因素。執行緒的數目可以通過環境變量或者代碼中的函數來指定。在C/C++中,OpenMP的函數都聲明在頭文件omp.h中。

語法

#pragma omp  [clause[[,] clause] ...]

directive

其中,directive共11個:
  • atomic 內存位置將會原子更新(Specifies that a memory location that will be updated atomically.)
  • barrier 執行緒在此等待,直到所有的執行緒都執行到此barrier。用來同步所有執行緒。
  • critical 其後的代碼塊為臨界區,任意時刻只能被一個執行緒執行。
  • flush 所有執行緒對所有共享對象具有相同的內存視圖(view of memory)
  • for 用在for循環之前,把for循環並行化由多個執行緒執行。循環變量只能是整型
  • master 指定由主執行緒來執行接下來的程式。
  • ordered 指定在接下來的代碼塊中,被並行化的 for循環將依序執行(sequential loop)
  • parallel 代表接下來的代碼塊將被多個執行緒並行各執行一遍。
  • sections 將接下來的代碼塊包含將被並行執行的section塊。
  • single 之後的程式將只會在一個執行緒(未必是主執行緒)中被執行,不會被並行執行。
  • threadprivate 指定一個變量是執行緒局部存儲(thread local storage)

clause

共計13個clause:
  • copyin 讓threadprivate的變量的值和主執行緒的值相同。
  • copyprivate 不同執行緒中的變量在所有執行緒中共享。
  • default Specifies the behavior of unscoped variables in a parallel region.
  • firstprivate 對於執行緒局部存儲的變量,其初值是進入並行區之前的值。
  • if 判斷條件,可用來決定是否要並行化。
  • lastprivate 在一個循環並行執行結束後,指定變量的值為循環體在順序最後一次執行時取得的值,或者#pragma sections在中,按文本順序最後一個section中執行取得的值。
  • nowait 忽略barrier的同步等待。
  • num_threads 設定執行緒數量的數量。默認值為當前計算機硬體支持的最大並發數。一般就是CPU的內核數目。超執行緒被作業系統視為獨立的CPU內核。
  • ordered 使用於 for,可以在將循環並行化的時候,將程式中有標記 directive ordered 的部份依序執行。
  • private 指定變量為執行緒局部存儲。
  • reduction Specifies that one or more variables that are private to each thread are the subject of a reduction operation at the end of the parallel region.
  • schedule 設定for循環的並行化方法;有 dynamic、guided、runtime、static 四種方法。
    • schedule(static, chunk_size) 把chunk_size數目的循環體的執行,靜態依序指定給各執行緒。
    • schedule(dynamic, chunk_size) 把循環體的執行按照chunk_size(預設值為1)分為若干組(即chunk),每個等待的執行緒獲得當前一組去執行,執行完後重新等待分配新的組。
    • schedule(guided, chunk_size) 把循環體的執行分組,分配給等待執行的執行緒。最初的組中的循環體執行數目較大,然後逐漸按指數方式下降到chunk_size。
    • schedule(runtime) 循環的並行化方式不在編譯時靜態確定,而是推遲到程序執行時動態地根據環境變量OMP_SCHEDULE 來決定要使用的方法。
  • shared 指定變量為所有執行緒共享。


OpenMP的庫函數

OpenMP定義了20多個庫函數:
1.void omp_set_num_threads(int _Num_threads);
在後續並行區域設置執行緒數,此調用只影響調用執行緒所遇到的同一級或內部嵌套級別的後續並行區域.說明:此函數只能在串行代碼部分調用.
2.int omp_get_num_threads(void);
返回當前執行緒數目.說明:如果在串行代碼中調用此函數,返回值為1.
3.int omp_get_max_threads(void);
如果在程序中此處遇到未使用 num_threads() 子句指定的活動並行區域,則返回程序的最大可用執行緒數量.說明:可以在串行或並行區域調用,通常這個最大數量由omp_set_num_threads()或OMP_NUM_THREADS環境變量決定.
4.int omp_get_thread_num(void);
返回當前執行緒id.id從1開始順序編號,主執行緒id是0.
5.int omp_get_num_procs(void);
返回程序可用的處理器數.
6.void omp_set_dynamic(int _Dynamic_threads);
啟用或禁用可用執行緒數的動態調整.(預設情況下啟用動態調整.)此調用只影響調用執行緒所遇到的同一級或內部嵌套級別的後續並行區域.如果 _Dynamic_threads 的值為非零值,啟用動態調整;否則,禁用動態調整.
7.int omp_get_dynamic(void);
確定在程序中此處是否啟用了動態執行緒調整.啟用了動態執行緒調整時返回非零值;否則,返回零值.
8.int omp_in_parallel(void);
確定執行緒是否在並行區域的動態範圍內執行.如果在活動並行區域的動態範圍內調用,則返回非零值;否則,返回零值.活動並行區域是指 IF 子句求值為 TRUE 的並行區域.
9.void omp_set_nested(int _Nested);
啟用或禁用嵌套並行操作.此調用只影響調用執行緒所遇到的同一級或內部嵌套級別的後續並行區域._Nested 的值為非零值時啟用嵌套並行操作;否則,禁用嵌套並行操作.預設情況下,禁用嵌套並行操作.
10.int omp_get_nested(void);
確定在程序中此處是否啟用了嵌套並行操作.啟用嵌套並行操作時返回非零值;否則,返回零值.
互斥鎖操作 嵌套鎖操作 功能
11.void omp_init_lock(omp_lock_t * _Lock); 12. void omp_init_nest_lock(omp_nest_lock_t * _Lock);
初始化一個(嵌套)互斥鎖.
13.void omp_destroy_lock(omp_lock_t * _Lock); 14.void omp_destroy_nest_lock(omp_nest_lock_t * _Lock);
結束一個(嵌套)互斥鎖的使用並釋放內存.
15.void omp_set_lock(omp_lock_t * _Lock); 16.void omp_set_nest_lock(omp_nest_lock_t * _Lock);
獲得一個(嵌套)互斥鎖.
17.void omp_unset_lock(omp_lock_t * _Lock); 18.void omp_unset_nest_lock(omp_nest_lock_t * _Lock);
釋放一個(嵌套)互斥鎖.
19.int omp_test_lock(omp_lock_t * _Lock); 20.int omp_test_nest_lock(omp_nest_lock_t * _Lock);
試圖獲得一個(嵌套)互斥鎖,並在成功時放回真(true),失敗是返回假(false).
21.double omp_get_wtime(void);
獲取wall clock time,返回一個double的數,表示從過去的某一時刻經歷的時間,一般用於成對出現,進行時間比較. 此函數得到的時間是相對於執行緒的,也就是每一個執行緒都有自己的時間.
22.double omp_get_wtick(void);
得到clock ticks的秒數.


例子

在 omp parallel 段內的程序代碼由多線程來執行:
 int main(int argc, char* argv[])
 {
  #pragma omp parallel  
   printf("Hello, world.\n");
 
   return 1;
 }
執行結果
% gcc omp.c (由單線程來執行)
% ./a.out
Hello, world.

% gcc -fopenmp omp.c (由多線程來執行)
% ./a.out
Hello, world.
Hello, world.
Hello, world.
Hello, world.

環境變量

OpenMP可以使用環境變量 OMP_NUM_THREADS以控制執行線程的數量。

例子

% gcc -fopenmp omp.c 

% setenv OMP_NUM_THREADS 2(由2線程來執行)
setenv是CSH的指令

在bash shell 環境中 要用export 
% export OMP_NUM_THREADS=2 (由2線程來執行)

% ./a.out
Hello, world.
Hello, world.

優點和缺點

優點
  • 可移植的多執行緒代碼(在C/C++和其他語言中,人們通常為了獲得多執行緒而調用特定於平台的原語)
  • 簡單,沒必要向MPI中那樣處理消息傳遞
  • 數據分布和分解由指令自動完成
  • 增量並行,一次可以只在代碼的一部分執行,對代碼不需要顯著的改變
  • 統一的順序執行和並行執行的代碼,在順序執行編譯器上,OpenMP的執行按照注釋進行對待;
  • 在一般情況下,使用OpenMP並行時原始的(串行)代碼語句不需要進行修改,這減少不經意間引入錯誤的機會。
  • 同時支持粗粒度和細粒度的並行
缺點
  • 存在引入難以調試的同步錯誤和競爭條件的風險
  • 目前,只能在共享內存的多處理器平台高效運行
  • 需要一個支持OpenMP的編譯器
  • 可擴展性是受到內存架構的限制
  • 不支持比較和交換
  • 缺乏可靠的錯誤處理
  • 缺乏對執行緒與處理器映射的細粒度控制
  • GPU上不能使用
  • 很容易出現一些不能共享的代碼
  • 多執行緒的可執行文件的啟動需要更多的時間,可能比單執行緒的運行的慢,因此,使用多執行緒一定要有其他有優勢的地方
  • 很多情況下使用多執行緒不僅沒有好處,還會帶來一些額外消耗

沒有留言: