OpenMP程序設計的兩個小技巧
1、動態設置并行循環的線程數量
在實際情況中,程序可能運行在不同的機器 環境里,有些機器是雙核,有些機器是4核甚至更多核。并且未來硬件存在升級的可能,CPU核數會變得越來越多。如何根據機器硬件的不同來自動設置合適的線 程數量就顯得很重要了,否則硬件升級后程序就得進行修改,那將是一件很麻煩的事情。
比如剛開始在雙核系統中開發的軟件,線程數量缺省都設成2,那么當機器升級到4核或8核以后,線程數量就不能滿足要求了,除非修改程序。
線程數量的設置除了要滿足機器硬件升級的可擴展性外,還需要考慮程序的可擴展性,當程序運算量增加或減少后,設置的線程數量仍然能夠滿足要求。顯然這也不能通過設置靜態的線程數量來解決。
在具體計算需要使用多少線程時,主要需要考慮以下兩點:
1)當循環次數比較少時,如果分成過多數量的線程來執行,可能會使得總運行時間高于較少線程或一個線程執行的情況。并且會增加能耗。
2)如果設置的線程數量遠大于CPU核數的話,那么存在著大量的任務切換和調度等開銷,也會降低整體效率。
那么如何根據循環的次數和CPU核數來動態地設置線程的數量呢?下面以一個例子來說明動態設置線程數量的算法,假設一個需要動態設置線程數的需求為:
1、 以多個線程運行時的每個線程運行的循環次數不低于4次
2、 總的運行線程數最大不超過2倍CPU核數
下面代碼便是一個實現上述需求的動態設置線程數量的例子
- const int MIN_ITERATOR_NUM = 4;
- int ncore = omp_get_num_procs(); //獲取執行核的數量
- int max_tn = n / MIN_ITERATOR_NUM;
- int tn = max_tn > 2*ncore ? 2*ncore : max_tn; //tn表示要設置的線程數量
- #pragma omp parallel for if( tn > 1) num_threads(tn)
- for ( i = 0; i < n; i++ )
- {
- printf("Thread Id = %ld/n", omp_get_thread_num());
- //Do some work here
- }
在上面代碼中,根據每個線程運行的循環次數不低于4次,先計算出最大可能的線程數max_tn,然后計算需要的線程數量tn,tn的值等于max_tn和2倍CPU核數中的較小值。
然后在parallel for構造中使用if子句來判斷tn是否大于1,大于1時使用單個線程,否則使用tn個線程,,這樣就使得設置的線程數量滿足了需求中的條件。
比如在一個雙核CPU上,n=64,最終會以2倍CPU核數(4個)線程運行,而不會以max_tn = 64/4=16個線程運行。
在實際情況中,當然不能每個循環都象上面一樣寫幾行代碼來計算一遍,可以將其寫成一個獨立的功能函數如下:
- const int g_ncore = omp_get_num_procs(); //獲取執行核的數量
- /** 計算循環迭代需要的線程數量
- 根據循環迭代次數和CPU核數及一個線程最少需要的循環迭代次數
- 來計算出需要的線程數量,計算出的最大線程數量不超過CPU核數
- @param int n - 循環迭代次數
- @param int min_n - 單個線程需要的最少迭代次數
- @return int - 線程數量
- */
- int dtn(int n, int min_n)
- {
- int max_tn = n / min_n;
- int tn = max_tn > g_ncore ? g_ncore : max_tn; //tn表示要設置的線程數量
- if ( tn < 1 )
- {
- tn = 1;
- }
- return tn;
- }
- 這樣每次并行化循環時就可以直接使用函數dtn()來獲取合適的線程數量,前面的代碼可以簡寫成如下形式:
- #pragma omp parallel for num_threads(dtn(n, MIN_ITERATOR_NUM))
- for ( i = 0; i < n; i++ )
- {
- printf("Thread Id = %ld/n", omp_get_thread_num());
- //Do some work here
- }
當然具體設置多少線程要視情況而定的,一般情況下線程數量剛好等于CPU核數可以取得比較好的性能,因為線程數等于CPU核數時,每個核執行一個任務,沒有任務切換開銷。
2、嵌套循環的并行化
在嵌套循環中,如果外層循環迭代次數較少時,如果將來CPU核數增加到一定程度時,創建的線程數將可能小于CPU核數。另外如果內層循環存在負載平衡的情況下,很難調度外層循環使之達到負載平衡。
下面以矩陣乘法作為例子來講述如何將嵌套循環并行化,以滿足上述擴展性和負載平衡需求。
其實可以采用一個簡單的方法將最外層循環和第2層循環合并成一個循環,下面便是采用合并循環后的并行實現。
- void Parallel_Matrix_Multiply(int *a, int row_a, int col_a,
- int *b, int row_b,int col_b,
- int *c, int c_size )
- {
- if ( col_a != row_b )
- {
- return;
- }
- int i, j, k;
- int index;
- int border = row_a * col_b;
- i = 0;
- j = 0;
- #pragma omp parallel private(i,j,k) num_threads(dtn(border, 1))
- for ( index = 0; index < border; index++ )
- {
- i = index / col_b;
- j = index % col_b;
- int row_i = i * col_a;
- int row_c = i * col_b;
- c[row_c+j] = 0;
- for ( k = 0; k < row_b; k++ )
- {
- c[row_c + j] += a[row_i+k] * b[k*col_b+j];
- }
- }
- }
從上面代碼可以看出,合并后的循環邊界border = row_a * col_b;即等于原來兩個循環邊界之積,然后在循環中計算出原來的外層循環和第2層循環的迭代變量i和j,采用除法和取余來求出i和j的值。
需要注意的是,上面求i和j的值必須要保證循環迭代的獨立性,即不能有循環迭代間的依賴關系。不能將求i和j值的過程優化成如下的形式:
- if ( j == col_b )
- {
- j = 0;
- i++;
- }
- // …… 此處代表實際的矩陣乘法代碼
- j++;
上面這種優化,省去了除法,效率高,但是只能在串行代碼中使用,因為它存在循環迭代間的依賴關系,無法將其正確地并行化。
原文鏈接:http://blog.csdn.net/drzhouweiming/article/details/2472454