天天看點

「并行學習」 OpenMPOpenMP

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

「并行學習」 OpenMPOpenMP
#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)執行的代碼。

「并行學習」 OpenMPOpenMP
  1. 當使

    parallel

    建構時,實際上産生了一系列的線程(threads)。
  2. parallel region中的代碼實際上會被多個線程執行,每個線程執行的是相同的代碼。
  3. 在并行的結束部分将會有一個barrier,使線程之間同步。
  4. 當一個線程終止時,所有的線程都會終止。

限制

  1. 不能在parallel region中調用其他檔案的function。
  2. 不能使用goto或者jump語句,因為這樣可能跳轉到非parallel區域。但可以調用其他的function call(雖然不建議)。

多少個Threads

  1. 首先看

    IF

    子句:如果是

    FALSE

    ,那麼并行部分将會被序列執行

    例如:

    #pragma omp parallel IF(para == true)

  2. 然後看

    num_threads()

    子句:

    例如:

    #pragma omp parallel num_threads(10)

  3. 然後看是否使用了

    omp_set_num_threads()

    庫函數:需要在執行parallel region之前執行。
  4. 檢視

    OMP_NUM_THREADS

    環境變量:需要在執行parallel region之前設定。
  5. 預設:使用一個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!");
  }
}
           
  1. 有時候,有些庫可能不支援這樣的嵌套操作,這個時候就可以使用

    omp_get_nested()

    函數,來檢查是否可以嵌套。
  2. 使用

    omp_set_nested(bool)

    來enable/disable parallel region;或通過設定

    OMP_NESTED

    環境變量。
  3. 如果嵌套是disable的狀态,或者是不support的,那麼在嵌套parallel region中隻會建立一個線程。

Work-Sharing Construct

定義:

  1. work-sharing construct将code region中的任務分開,讓不同的線程執行不同的任務。
  2. work-sharing construct不會建立新的threads,建立threads的工作是parallel做的。
  3. 同 Parallel Construct一樣,雖然在進入程式時沒有barrier,但是在程式執行的最終階段會有一個barrier(但是可以用clause來override這個設定)。

DO/for Directive

是資料的并行。隻要資料和資料之間沒有dependency,就可以使用

DO/for

來讓每一個線程出來一部分的資料。

「并行學習」 OpenMPOpenMP

DO/for Directive Specific Clauses:

  1. nowait

    :最後沒有了barrier,不進行同步。
  2. schedule

    :描述iterations是怎麼在threads之間配置設定的。
  3. ordered

    :不同的線程并發執行,直到遇到該

    ordered

    區域為止,然後按與在串行循環中執行順序相同的順序依次執行該區域。這仍然允許一定程度的并發性,尤其是在該區域之外的代碼段

    ordered

    具有大量運作時間的情況下。
  4. collapse

    :使用在嵌套循環中。實際上是将多層的嵌套轉換為一個一層的特别長的嵌套。

Schedule Clause

  1. STATIC

    schedule(static, chunk-size)

    循環結構的子句指定for循環具有靜态排程類型。OpenMP将疊代劃分為多個大小塊,

    chunk-size

    并将其按循環順序配置設定給線程。

    如果未

    chunk-size

    指定 ,則OpenMP将疊代劃分為大小近似相等的塊,并且最多将一個塊配置設定給每個線程。
  2. DYNAMIC

    當一個線程完成了一個

    chunk

    ( default size:1 )後,就會動态的配置設定給它其他的任務。先做完的便可以做更多的iteration,保證了每個thread做的任務的平均。
  3. GUIDED

    DYNAMIC

    類似。由于任務随着執行越來越少,是以做到後面就不需要很大的

    chunk -size

    了,這就是

    GUIDEDd

    的做法,随着任務的執行,任務總量的減少,逐漸減小

    chunk-size

  4. RUNTIME

    運作時(runtime)根據系統變量

    OMP_SCHEDULE

    來決定使用哪種scheduling方法。
  5. 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

的使用
#pragma omp parallel for 
for (int i = 0; i < 10; i++)
	printf("i=%d, thread = %d\n",i, omp_get_thread_num());
           

執行結果:

「并行學習」 OpenMPOpenMP
#pragma omp parallel for order 
for (int i = 0; i < 3; i++)
	printf("i=%d, thread = %d\n",i, omp_get_thread_num());
           

執行結果:

「并行學習」 OpenMPOpenMP

兩者的差別在于使用order後,執行到order的代碼區會按照循環的方式串行化(但是前面執行的過程仍然是并行的)。可以看下這個網頁。

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());
           
「并行學習」 OpenMPOpenMP
#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());
           
「并行學習」 OpenMPOpenMP

使用

collapse()

來進行嵌套循環,在含有

collapse()

的 statement和循環之間不能再加其他的循環。

collapse()

的參數是collapse在一起的循環的層級數。如果不是用

collapse()

,即第一個例子中,編譯器隻會識别到第一層循環,也就是說,隻有第一次循環會被配置設定到不同的線程中,第二層循環不會被配置設定到不同線程中,而是一次性丢給第一次循環對應的線程。相對應的,第二個例子使用了

collapse()

,兩個循環對應9個任務,将會被獨立的配置設定給不同的線程。

對于不同的循環,要對應的選擇是否使用

collapse()

。比如,如果第二個循環的執行順序将影響執行結果,那麼顯然就不應該使用

collapse()

了。

SECTIONS

是function call這個等級上的并行。也就是說,一個section,也就是一個function call,就需要一個線程來執行。同時,每一個section中執行的function call是不同的,需要單獨寫出。每個section是不會重複做的!!

  1. 是一種非循環疊代式的work-sharing的construct。
  2. 每一個section of code都将被配置設定到不同的threads中執行。
  3. 每一個獨立的section都在sections指令中嵌套定義。
  4. 每一個section都被一個thread執行僅一次。
# pragma omp sections[clause......]
{
	#pragma omp section
		/*structured_block*/
	#pragma omp section
		/*structured_block*/
}
           
「并行學習」 OpenMPOpenMP
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的特例。

  1. 執行時,其中一個thread将會串行執行這一個single,而其他的thread視作沒有看到它。
  2. 對于其他不執行single的thread,除非有nowait子句,否則他們等待single的這段代碼執行完。
「并行學習」 OpenMPOpenMP
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 */
           

Synchronization Construct