OpenMP中,任務調度主要用於並行的for迴圈中,當迴圈中每次迭代的計算量不相等時,如果簡單地給各個線程分配相同次數的迭代的話,會造成各個線程計算負載不均衡,這會使得有些線程先執行完,有些後執行完,造成某些CPU核空閑,影響程式效能。例如以下代碼:
int i, j;
int a[100][100] = {0};
for ( i =0; i < 100; i++)
{
for( j = i; j < 100; j++ )
{
a[i][j] = i*j;
}
}
如果將最外層迴圈並行化的話,比如使用4個線程,如果給每個線程平均分配25次迴圈迭代計算的話,顯然i=0和i=99的計算量相差了100倍,那麼各個線程間可能出現較大的負載不平衡情況。為瞭解決這些問題,OpenMP中提供了幾種對for迴圈並行化的任務調度方案。
在OpenMP中,對for迴圈並行化的任務調度使用schedule子句來實現,下面介紹schedule字句的用法。
Schedule子句用法
schedule子句的使用格式為:
schedule(type[,size])
schedule有兩個參數:type和size,size參數是可選的。
1. type參數
表示調度類型,有四種調度類型如下:
· dynamic
· guided
· runtime
· static
這四種調度類型實際上只有static、dynamic、guided三種調度方式,runtime實際上是根據環境變數來選擇前三種中的某中類型。
run-sched-var
2. size參數 (可選)
size參數表示迴圈迭代次數,size參數必須是整數。static、dynamic、guided三種調度方式都可以使用size參數,也可以不使用size參數。當type參數類型為runtime時,size參數是非法的(不需要使用,如果使用的話編譯器會報錯)。
靜態調度(static)
當parallel for編譯指導語句沒有帶schedule子句時,大部分系統中預設採用static調度方式,這種調度方式非常簡單。假設有n次迴圈迭代,t個線程,那麼給每個線程靜態分配大約n/t次迭代計算。這裡為什麼說大約分配n/t次呢?因為n/t不一定是整數,因此實際分配的迭代次數可能存在差1的情況,如果指定了size參數的話,那麼可能相差一個size。
靜態調度時可以不使用size參數,也可以使用size參數.
1. 不使用size參數
不使用size參數時,分配給每個線程的是n/t次連續的迭代,不使用size參數的用法如下:
schedule(static)
例如以下代碼:
#pragma omp parallel for schedule(static)
for(i = 0; i < 10; i++ )
{
printf("i=%d, thread_id=%d\n", i, omp_get_thread_num());
}
上面代碼執行時列印的結果如下:
i=0, thread_id=0
i=1, thread_id=0
i=2, thread_id=0
i=3, thread_id=0
i=4, thread_id=0
i=5, thread_id=1
i=6, thread_id=1
i=7, thread_id=1
i=8, thread_id=1
i=9, thread_id=1
可以看出線程0得到了0~4次連續迭代,線程1得到5~9次連續迭代。注意由於多線程執行時序的隨機性,每次執行時列印的結果順序可能存在差別,後面的例子也一樣。
2. 使用size參數
使用size參數時,分配給每個線程的size次連續的迭代計算,用法如下:
schedule(static, size)
例如以下代碼:
#pragma omp parallel for schedule(static, 2)
for(i = 0; i < 10; i++ )
{
printf("i=%d, thread_id=%d\n", i, omp_get_thread_num());
}
執行時會列印以下結果:
i=0, thread_id=0
i=1, thread_id=0
i=4, thread_id=0
i=5, thread_id=0
i=8, thread_id=0
i=9, thread_id=0
i=2, thread_id=1
i=3, thread_id=1
i=6, thread_id=1
i=7, thread_id=1
從列印結果可以看出,0、1次迭代分配給線程0,2、3次迭代分配給線程1,4、5次迭代分配給線程0,6、7次迭代分配給線程1,…。每個線程依次分配到2次連續的迭代計算。
動態調度(dynamic)
動態調度是動態地將迭代分配到各個線程,動態調度可以使用size參數也可以不使用size參數,不使用size參數時是將迭代逐個地分配到各個線程,使用size參數時,每次分配給線程的迭代次數為指定的size次。
下面為使用動態調度不帶size參數的例子:
#pragma omp parallel for schedule(dynamic)
for(i = 0; i < 10; i++ )
{
printf("i=%d, thread_id=%d\n", i, omp_get_thread_num());
}
列印結果如下:
i=0, thread_id=0
i=1, thread_id=1
i=2, thread_id=0
i=3, thread_id=1
i=5, thread_id=1
i=6, thread_id=1
i=7, thread_id=1
i=8, thread_id=1
i=4, thread_id=0
i=9, thread_id=1
下面為動態調度使用size參數的例子:
#pragma omp parallel for schedule(dynamic, 2)
for(i = 0; i < 10; i++ )
{
printf("i=%d, thread_id=%d\n", i, omp_get_thread_num());
}
列印結果如下:
i=0, thread_id=0
i=1, thread_id=0
i=4, thread_id=0
i=2, thread_id=1
i=5, thread_id=0
i=3, thread_id=1
i=6, thread_id=0
i=8, thread_id=1
i=7, thread_id=0
i=9, thread_id=1
從列印結果可以看出第0、1,4、5,6、7次迭代被分配給了線程0,第2、3,8、9次迭代則分配給了線程1,每次分配的迭代次數為2。
guided調度(guided)
guided調度是一種採用指導性的啟發學習法自調度方法。開始時每個線程會分配到較大的迭代塊,之後分配到的迭代塊會逐漸遞減。迭代塊的大小會按指數級下降到指定的size大小,如果沒有指定size參數,那麼迭代塊大小最小會降到1。
例如以下代碼:
#pragma omp parallel for schedule(guided,2)
for(i = 0; i < 10; i++ )
{
printf("i=%d, thread_id=%d\n", i, omp_get_thread_num());
}
列印結果如下:
i=0, thread_id=0
i=1, thread_id=0
i=2, thread_id=0
i=3, thread_id=0
i=4, thread_id=0
i=8, thread_id=0
i=9, thread_id=0
i=5, thread_id=1
i=6, thread_id=1
i=7, thread_id=1
第0、1、2、3、4次迭代被分配給線程0,第5、6、7次迭代被分配給線程1,第8、9次迭代被分配給線程0,分配的迭代次數呈遞減趨勢,最後一次遞減到2次。
runtime調度(rumtime)
runtime調度並不是和前面三種調度方式似的真實調度方式,它是在運行時根據環境變數OMP_SCHEDULE來確定調度類型,最終使用的調度類型仍然是上述三種調度方式中的某種。
例如在unix系統中,可以使用setenv命令來設定OMP_SCHEDULE環境變數:
setenv OMP_SCHEDULE “dynamic, 2”
上述命令設定調度類型為動態調度,動態調度的迭代次數為2。
在windows環境中,可以在”系統屬性|進階|環境變數”對話方塊中進行設定環境變數。