OpenMP
What’s OpenMP
OpenMP == Open specification for Multi-Processing
An API : multi-threaded, shared memory parallelism
Portable: the API is specified for C/C++ and Fortran
Fork-Join model: the master thread forks a specified number of slave threads and divides task among them
Compiler Directive(訓示) Based: Compiler takes care of generating code that forks/joins threads and divide tasks to threads
#include <omp.h>
// Serial code
int A[10], B[10], C[10];
// Beginning of parallel section. Fork a team of threads.
#pragma omp parallel for num_threads(10)
{
for (int i=0; i<10; i++)
A[i] = B[i] + C[i];
} /* All threads join master thread and terminate */
OpenMP Directives(C/C++ Format):
#pragma omp | Directive-name | [clause,…] | newline |
---|---|---|---|
Required. | Valid OpenMP directive: parallel, do, for. | Optional: Clauses can be in any order, and repeated as necessary. | Required. |
每一個directive後面隻能接一個succeeding statement,必須是被structured block的。
Parallel Region Constructs
Parallel Directive
并行區域是一塊通過多線程(multiple threads)執行的代碼。
- 當使
建構時,實際上産生了一系列的線程(threads)。parallel
- parallel region中的代碼實際上會被多個線程執行,每個線程執行的是相同的代碼。
- 在并行的結束部分将會有一個barrier,使線程之間同步。
- 當一個線程終止時,所有的線程都會終止。
限制
- 不能在parallel region中調用其他檔案的function。
- 不能使用goto或者jump語句,因為這樣可能跳轉到非parallel區域。但可以調用其他的function call(雖然不建議)。
多少個Threads
- 首先看
子句:如果是IF
FALSE
,那麼并行部分将會被序列執行
例如:
#pragma omp parallel IF(para == true)
- 然後看
num_threads()
子句:
例如:
#pragma omp parallel num_threads(10)
- 然後看是否使用了
庫函數:需要在執行parallel region之前執行。omp_set_num_threads()
- 檢視
環境變量:需要在執行parallel region之前設定。OMP_NUM_THREADS
- 預設:使用一個node上CPU的個數
嵌套調用Parallel Region
// A total of 6 “hello world!” is printed
#pragma omp parallel num_threads(2)
{
{
#pragma omp parallel num_threads(3)
printf("hello world!");
}
}
- 有時候,有些庫可能不支援這樣的嵌套操作,這個時候就可以使用
函數,來檢查是否可以嵌套。omp_get_nested()
- 使用
來enable/disable parallel region;或通過設定omp_set_nested(bool)
環境變量。OMP_NESTED
- 如果嵌套是disable的狀态,或者是不support的,那麼在嵌套parallel region中隻會建立一個線程。
Work-Sharing Construct
定義:
- work-sharing construct将code region中的任務分開,讓不同的線程執行不同的任務。
- work-sharing construct不會建立新的threads,建立threads的工作是parallel做的。
- 同 Parallel Construct一樣,雖然在進入程式時沒有barrier,但是在程式執行的最終階段會有一個barrier(但是可以用clause來override這個設定)。
DO/for Directive
是資料的并行。隻要資料和資料之間沒有dependency,就可以使用
DO/for
來讓每一個線程出來一部分的資料。
DO/for Directive Specific Clauses:
-
:最後沒有了barrier,不進行同步。nowait
-
:描述iterations是怎麼在threads之間配置設定的。schedule
-
:不同的線程并發執行,直到遇到該ordered
區域為止,然後按與在串行循環中執行順序相同的順序依次執行該區域。這仍然允許一定程度的并發性,尤其是在該區域之外的代碼段ordered
具有大量運作時間的情況下。ordered
-
:使用在嵌套循環中。實際上是将多層的嵌套轉換為一個一層的特别長的嵌套。collapse
Schedule Clause
-
:STATIC
循環結構的子句指定for循環具有靜态排程類型。OpenMP将疊代劃分為多個大小塊,schedule(static, chunk-size)
chunk-size
并将其按循環順序配置設定給線程。
如果未
指定 ,則OpenMP将疊代劃分為大小近似相等的塊,并且最多将一個塊配置設定給每個線程。chunk-size
-
DYNAMIC
:
當一個線程完成了一個
( default size:1 )後,就會動态的配置設定給它其他的任務。先做完的便可以做更多的iteration,保證了每個thread做的任務的平均。chunk
-
GUIDED
:
與
類似。由于任務随着執行越來越少,是以做到後面就不需要很大的DYNAMIC
了,這就是chunk -size
的做法,随着任務的執行,任務總量的減少,逐漸減小GUIDEDd
。chunk-size
-
RUNTIME
:
運作時(runtime)根據系統變量
來決定使用哪種scheduling方法。OMP_SCHEDULE
-
AUTO
:
完全由編譯器決定scheduling方法(不推薦使用 不夠智能嗷)。
Scheduling Examples
A for loop with 100 iterations and 4 threads:
schedule(static, 10)
Thread0: Iter0-10, Iter40-50, Iter80-90
Thread0: Iter10-20, Iter50-60, Iter90-100
Thread0: Iter20-30, Iter60-70
Thread0: Iter30-40, Iter70-80
schedule(dynamic, 10) 由于一些threads做的比較快,是以這些threads做的較多,而另一些做的比較少。
Thread0: Iter0-10, Iter70-80, Iter80-90, Iter90-100
Thread0: Iter10-20, Iter50-60
Thread0: Iter20-30, Iter60-70
Thread0: Iter30-40, Iter40-50
schedule(guided, 10)
Thread0: Iter0-10, Iter40-50, Iter80-85
Thread0: Iter10-20, Iter50-60, Iter85-90
Thread0: Iter20-30, Iter60-70, Iter90-95
Thread0: Iter30-40, Iter70-80, Iter95-100
Do/for Directive Examples
一個典型程式
#define CHUNKSIZE 100
#define N 1000
int main () {
int a[N], b[N], c[N];
/* Some initializations */
for (int i=0; i < N; i++)
a[i] = b[i] = i;
int chunk = CHUNKSIZE;
int thread = NUM_THREAD;
#pragma omp parallel num_thread(thread) shared(a,b,c) private(i)
{
#pragma omp for schedule(dynamic,chunk) nowait
for (int i=0; i < N; i++)
c[i] = a[i] + b[i];
} /* end of parallel section */
}
order
的使用
order
#pragma omp parallel for
for (int i = 0; i < 10; i++)
printf("i=%d, thread = %d\n",i, omp_get_thread_num());
執行結果:
#pragma omp parallel for order
for (int i = 0; i < 3; i++)
printf("i=%d, thread = %d\n",i, omp_get_thread_num());
執行結果:
兩者的差別在于使用order後,執行到order的代碼區會按照循環的方式串行化(但是前面執行的過程仍然是并行的)。可以看下這個網頁。
collapse
的使用:
collapse
#pragma omp parallel num_thread(6)
#pragma omp for schedule(dynamic)
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
printf("i=%d, j=%d, thread = %d\n",i, j, omp_get_thread_num());
#pragma omp parallel num_thread(6)
#pragma omp for schedule(dynamic) collapse(2)
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
printf("i=%d, j=%d, thread = %d\n", i, j, omp_get_thread_num());
使用
collapse()
來進行嵌套循環,在含有
collapse()
的 statement和循環之間不能再加其他的循環。
collapse()
的參數是collapse在一起的循環的層級數。如果不是用
collapse()
,即第一個例子中,編譯器隻會識别到第一層循環,也就是說,隻有第一次循環會被配置設定到不同的線程中,第二層循環不會被配置設定到不同線程中,而是一次性丢給第一次循環對應的線程。相對應的,第二個例子使用了
collapse()
,兩個循環對應9個任務,将會被獨立的配置設定給不同的線程。
對于不同的循環,要對應的選擇是否使用
collapse()
。比如,如果第二個循環的執行順序将影響執行結果,那麼顯然就不應該使用
collapse()
了。
SECTIONS
是function call這個等級上的并行。也就是說,一個section,也就是一個function call,就需要一個線程來執行。同時,每一個section中執行的function call是不同的,需要單獨寫出。每個section是不會重複做的!!
- 是一種非循環疊代式的work-sharing的construct。
- 每一個section of code都将被配置設定到不同的threads中執行。
- 每一個獨立的section都在sections指令中嵌套定義。
- 每一個section都被一個thread執行僅一次。
# pragma omp sections[clause......]
{
#pragma omp section
/*structured_block*/
#pragma omp section
/*structured_block*/
}
int N = 1000
int a[N], b[N], c[N], d[N];
#pragma omp parallel num_thread(2) shared(a,b,c,d) private(i)
{
#pragma omp sections /* specify sections*/
{
#pragma omp section /* 1st section*/
{
for (int i=0; i < N; i++) c[i] = a[i] + b[i];
}
#pragma omp section /* 2nd section*/
{
for (int i=0; i < N; i++) d[i] = a[i] + b[i];
}
} /* end of section */
}/* end of parallel section */
SINGLE
是SECTIONS的特例。
- 執行時,其中一個thread将會串行執行這一個single,而其他的thread視作沒有看到它。
- 對于其他不執行single的thread,除非有nowait子句,否則他們等待single的這段代碼執行完。
int input;
#pragma omp parallel num_thread(10) shared(input)
{
// computing code that can be prcessed in parallel
#pragma omp single /* specify section */
{
scanf("%d", &input);
} /* end of seralized I/O call */
printf(“input is %d”, input);
} /* end of parallel section */