OpenMP並行構造的schedule子句詳解

schedule子句是專門爲循環並行構造的時候使用的子句,只能用於循環並行構造(parallel for)中。

根據OpenMP Spec(http://openmp.org/mp-documents/OpenMP3.1-CCard.pdf)可以知道:

schedule的語法爲:

schedule(kind[, chunk_size])

kind:

• static: Iterations are divided into chunks of size chunk_size. Chunks are assigned to threads in the team in round-robin fashion in order of thread number.
• dynamic: Each thread executes a chunk of iterations then requests another chunk until no chunks remain to be distributed.
• guided: Each thread executes a chunk of iterations then requests another chunk until no chunks remain to be assigned. The chunk sizes start large and shrink to the indicated chunk_size as chunks are scheduled.
• auto: The decision regarding scheduling is delegated to the compiler and/or runtime system.
• runtime: The schedule and chunk size are taken from the run-sched-var ICV

(1)schedule的作用:

上面知道,schedule只能用於循環並行構造中,其作用是用於控制循環並行結構的任務調度。一個簡單的理解,一個for循環假設有10此迭代,使用4個線程去執行,那麼哪些線程去執行哪些迭代呢?可以通過schedule去控制迭代的調度和分配,從而適應不同的使用情況,提高性能。

(1)schedule語法:

schedule(kind [, chunk_size]),其中kind可以取值爲static,dynamic,guided,auto,runtime。其中,測試發現,auto貌似是不行的(可能是Fortran才支持?不清楚)。參數chunk_size是一個size的參數,所以是整數了。這裏的runtime在後面分析,其實本質上是前面三種類型之一。

所以,schedule的類型一共是三種:static、dynamic、guided。下面先逐一分析這三種,最後再來了解runtime的含義。

(2)靜態調度static:

大部分的編譯器實現,在沒有使用schedule子句的時候,系統就是採用static方式調度的。

對於schedule(static,size)的含義,OpenMP會給每個線程分配size次迭代計算。這個分配是靜態的,“靜態”體現在這個分配過程跟實際的運行是無關的,可以從邏輯上推斷出哪幾次迭代會在哪幾個線程上運行。具體而言,對於一個N次迭代,使用M個線程,那麼,[0,size-1]的size次的迭代是在第一個線程上運行,[size, size + size -1]是在第二個線程上運行,依次類推。那麼,如果M太大,size也很大,就可能出現很多個迭代在一個線程上運行,而某些線程不執行任何迭代。需要說明的是,這個分配過程就是這樣確定的,不會因爲運行的情況改變,比如,我們知道,進入OpenMP後,假設有M個線程,這M個線程開始執行的時間不一定是一樣的,這是由OpenMP去調度的,並不會因爲某一個線程先被啓動,而去改變for的迭代的分配,這就是靜態的含義。分析下面的例子:

  1. #include <omp.h>   
  2. #define COUNT 4*3   
  3.   
  4. int main(int argc, _TCHAR* argv[])    
  5. {  
  6. #pragma omp parallel for schedule(static,4)   
  7.     for(int i = 0;i < COUNT; i++)   
  8.     {  
  9.         printf("Thread: %d, Iteration: %d\n", omp_get_thread_num(), i);  
  10.     }  
  11.   
  12.     return 0;    
  13. }  
#include <omp.h>
#define COUNT 4*3

int main(int argc, _TCHAR* argv[])  
{
#pragma omp parallel for schedule(static,4)
	for(int i = 0;i < COUNT; i++) 
	{
		printf("Thread: %d, Iteration: %d\n", omp_get_thread_num(), i);
	}

	return 0;  
}
下面是其中多次運行的幾次結果(四核系統,所以線程數量爲4):


從左到由假設爲結果1/2/3,從結果可以看到,無論是哪一個線程先啓動,team內的ID爲0的線程,總是會執行0,1,2,3對應的迭代,team內ID爲1的線程,總是會執行4,5,6,7對應的迭代,team內ID爲2的線程,總是會執行8,9,10,11的線程,而team內線程ID爲4的線程,並沒有去執行任何迭代。如果把上面的size的大小改爲12,那麼無論如何,所有的迭代都只會在線程0上執行,其實就跟串行的效果一樣了。對於這裏的情況,4個線程,卻只使用了3個線程去計算,所以這樣分配當然是不平衡的。

上面是針對給定size的情況,如果不指定size,只是指定static類型,那麼OpenMP爲使用迭代數/線程數作爲size的值,採取同樣的策略來進行分配,這樣每個線程執行的迭代數目就是一樣的(注意,如果迭代數/線程數不是整除的,那就不完全一樣了,但是整體是比較平衡的),一般而言,這就是不加任何schedule修飾下的調度情況了。

(3)動態調度dynamic

動態調度迭代的分配是依賴於運行狀態進行動態確定的,所以哪個線程上將會運行哪些迭代是無法像靜態一樣事先預料的。對於dynamic,沒有size參數的情況下,每個線程按先執行完先分配的方式執行1次循環,比如,剛開始,線程1先啓動,那麼會爲線程1分配一次循環開始去執行(i=0的迭代),然後,可能線程2啓動了,那麼爲線程2分配一次循環去執行(i=1的迭代),假設這時候線程0和線程3沒有啓動,而線程1的迭代已經執行完,可能會繼續爲線程1分配一次迭代,如果線程0或3先啓動了,可能會爲之分配一次迭代,直到把所有的迭代分配完。所以,動態分配的結果是無法事先知道的,因爲我們無法知道哪一個線程會先啓動,哪一個線程執行某一個迭代需要多久等等,這些都是取決於系統的資源、線程的調度等等。看下面的例子:

  1. #include <omp.h>   
  2. #include <Windows.h>   
  3. #define COUNT 4*3   
  4.   
  5. int main(int argc, _TCHAR* argv[])    
  6. {  
  7. #pragma omp parallel for schedule(dynamic)   
  8.     for(int i = 0;i < COUNT; i++)   
  9.     {  
  10.         printf("Thread: %d, Iteration: %d\n", omp_get_thread_num(), i);  
  11.     }  
  12.   
  13.     return 0;    
  14. }  
#include <omp.h>
#include <Windows.h>
#define COUNT 4*3

int main(int argc, _TCHAR* argv[])  
{
#pragma omp parallel for schedule(dynamic)
	for(int i = 0;i < COUNT; i++) 
	{
		printf("Thread: %d, Iteration: %d\n", omp_get_thread_num(), i);
	}

	return 0;  
}
下面也是多次運行的結果:


事實上,這個結果容易給人誤解,以結果2來分析,爲什麼線程1先執行完,其對應的迭代卻是1而不是0呢?而且接下來也是3而不是2呢?不是先執行完的先分配麼?我的理解是,這裏,剛開始的時候,我們不知道線程的狀態,實際的一種可能是,線程0先啓動了,所以會去執行迭代0裏面的內容,但是,只是開始去執行,而這裏用printf測試,是輸出的結果,輸出的順序不代表開始執行此迭代的順序,所以可能的情況是,線程0執行迭代0還沒有完成的時候,線程1空閒,爲線程1分配了迭代1,然後線程1一直運行迭代1的時候,線程0仍然在運行迭代0,這中間,線程2可能會分配了迭代2開始執行,線程1又獲得了分配機會,被分配了迭代3等等。。。總之,這裏的輸出順序是不代表每一個迭代開始被分配執行的時間順序的。總之,理解這個動態的過程,簡單理解,就是“誰有空,給誰分配一次迭代讓它去跑!" :)

那麼同樣,dynamic也可以有一個size參數,size表示,每次線程執行完(空閒)的時候給其一次分配的迭代的數量,如果沒有知道size(上面的分析),那麼每次就分配一個迭代。有了前面的理解,這個size的含義是很容易理解的了。

(4)guided調度

類似於動態調度,但每次分配的循環次數不同,開始比較大,以後逐漸減小。size表示每次分配的迭代次數的最小值,由於每次分配的迭代次數會逐漸減少,較少到size時,將不再減少。如果不知道size的大小,那麼默認size爲1,即一直減少到1。具體是如何減少的,以及開始比較大(具體是多少?),參考相關手冊的信息。

(5)runtime

runtime表示根據環境變量確定上述調度策略中的某一種,默認也是靜態的(static)。

控制schedule環境變量的是OMP_SCHEDULE環境變量,其值和上面的三中類型一樣了,比如:

setenv OMP_SCHEDULE “dynamic, 5”

就是schedule(dynamic,5)的含義了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章