使用openmp進行並行編程
預處理指令pragma
在系統中加入預處理器指令一般是用來允許不是基本c語言規範部分的行為。不支援pragma的編譯器會忽略pragma指令提示的那些語句,這樣就允許使用pragma的程式在不支援它們的平台上運行。
第一個程式:hello
#include <stdio.h>
#include <stdlib.h>
#include <omp.h>
void Hello(void); // Thread function
int main(int argc, char* argv[]) {
// Get number of threads from command line
int thread_count = strtol(argv[1], NULL, 10);
#pragma omp parallel num_threads(thread_count)
Hello();
return 0;
}
void Hello(void) {
int my_rank = omp_get_thread_num();
int thread_count = omp_get_num_threads();
printf("Hello from thread %d of %dnn\n", my_rank, thread_count);
}
Hello例子的分析:
最基本的並行原語
用於運行程式碼塊的執行緒數可以動態生成。
pragma omp parallel :
當程式到達parallel指令時,原來的執行緒繼續執行,另外的執行緒被啟動。在openmp語法中,執行並行塊的執行緒集合(原始執行緒和新的執行緒被稱為執行緒組,原始的執行緒被稱為主執行緒,額外的執行緒稱為從執行緒。每個執行緒組成員都調用指令後的程式碼塊。
num_thread( )
# pragma omp parallel num_threads ( thread_count )
一個從句例子(用於修飾原語),可用於指定執行緒數量
omp.h
#include <omp.h>
使用openmp必須含omp.h頭文件
strtol( )
long strtol(const char* number p,char** end p,int base);
使用stdlib.h中的strtol來獲得執行緒數
ps:一些系統因素可能會限制可以啟動的執行緒數量;OpenMP 並不保證能夠啟動指定個的執行緒;
多數系統能夠啟動上百甚至上千的執行緒;除非啟動的太多,一般都能滿足要求。
例子:梯形積分法
![]()
如果每個子區間有相同的寬度,並且定義h=(b-a)/n,xi=a+ih,i=0, 1, …, n,那麼近似值將是:
![]()
//串列演算法實現//
Input: a, b, n ;
h = (b*a)/n;
approx = (f(a) + f(b))/2.0;
for (i = 1; i <= n-1; i++) {
x_i = a + i*h;
approx += f(x_i);
}
approx = h*approx;
第一種嘗試
- 定義兩種類型的任務:
a) 計算單個梯形的面積;
b) 將面積加起來。
- 在第一階段,沒有通訊開銷;但第二階段每個任務需要通訊。
考慮一個問題:結果不可預估——引入互斥量
pragma omp critical global_result += my_result ;
第一個版本
#include <stdio.h>
#include <stdlib.h>
#include <omp.h>
void Trap(double a, double b, int n, double global_result p);
int main(int argc, char argv[]){
double global_result = 0.0;
double a, b;
int n;
int thread_count;
thread_count = strtol(argv[1], NULL, 10);
printf("Enter a, b, and n n");
scanf("%lf %lf %d", &a, &b, &n);
# pragma omp parallel num_threads(thread_count)
Trap(a, b, n, &global_result);
printf("With n = %d trapezoids, our estimate n", n);
printf("of the integral from %f to %f = %.14e n",
a, b, global_result);
return 0;
} /∗ main ∗/
void Trap(double a, double b, int n, double* global_result_p)
double h, x, my_result;
double local_a, local_b;
int i, local n;
int my_rank = omp_get_thread_num();
int thread_count = omp_get_num_threads();
h = (b−a)/n;
local_n = n/thread_count;
local_a = a + my_rank*local_n*h;
local_b = local_a + local_n*h;
my_result = (f(local_a) + f(local_b))/2.0;
for (i = 1; i <= local_n−1; i++){
x = local_a + i*h;
my_result += f(x);
}
` ` my_result = my_result*h;
# pragma omp critical
∗global_result_p += my_result;
} /∗ Trap ∗/
作用域
在串列程式中, 變數的作用域包含了所有可以使用變數的區域;
在OpenMP中, 變數的作用域還要包括可以訪問該變數的並行區域。
能被所有執行緒訪問的變數具有 shared(共享) 作用域;
只能被一個執行緒訪問的變數具有 private (私有)作用域.
默認的作用域是 shared.
規約從句:
替代(在parallel塊中聲明一個私有變數和將臨界區移到函數調用之)
歸約:將相同的歸約操作符重複的應用到操作數序列來得到一個結果的計算。
所有操作的中間結果存儲在一個變數中:歸約變數
reduction(<operator>:<variable list>)
新的程式碼:
global_result = 0.0;
# pragma omp parallel num threads(thread count)\
reduction(+: global_result)
global_result += Local_trap(double a, double b, int n);
parallel for
能夠生成一隊執行緒來執行接下來的語句塊;
語句塊必須是一個for循環;
通過將循環切分給不同的執行緒來實現並行。
只有迭代次數確定的循環才可以被並行化。
h = (b−a)/n;
approx = (f(a) + f(b))/2.0;
# pragma omp parallel for num threads(thread_count) reduction(+: approx)
for (i = 1; i <= n−1; i++)
approx += f(a + i∗h); approx = h∗approx;
可被並行化的for循環形式:
![]()
**ps: **index 必須是整數或者指針 (e.g., 不能是浮點數);
start, end, 和 incr 必須具有相應的類型。 例如, 如果index 是一個指針, 那麼 incr 必須是一個整型;
start, end, 和 incr 在循環執行過程中不能被修改;
在循環執行過程中, 變數 index 只能被for語句修改。
數據依賴
1.OpenMP 編譯器並不檢查循環迭代中的數據依賴問題;
2.一般來說,OpenMP無法處理帶有數據依賴的循環。
解決思路:設計私有變數並且保證其私有作用域(private子句)
default子句
編譯器強制要求程式設計師指定在塊中使用的外部變數的作用範圍。
double sum = 0.0;
# pragma omp parallel for num threads(thread count)\
default(none) reduction(+:sum) private(k, factor)\
shared(n)
for (k = 0; k < n; k++){
if (k % 2 == 0)
factor = 1.0;
else
factor = −1.0;
sum += factor/(2∗k+1);
}
for指令
並不創建執行緒,使用已經在parallel塊中創建的執行緒。
# pragma omp for
解決循環調用問題:schedule ( type , chunksize )
type 可以是:
static: 提前把任務分配好;
dynamic or guided: 在運行時動態分配;
dynamic:
任務被分成 chunksize 大小的連續段;
每個執行緒執行一小塊, 當有一個執行緒執行完時, 它會請求獲得1個新的;
重複上述過程,直到完成計算;
chunksize 可以被去掉;當去掉時, chunksize 默認為1.
guided:
每個執行緒執行一小塊, 當有一個執行緒執行完時, 它會請求獲得1個新的;
但是,新的任務塊是不斷變小的;
如果不指定chunksize,那麼默認會降到1.
如果指定了chunksize, 則會降到指定的chunksize, 除了最後一塊可能小於chunksize.
auto: 編譯器或者運行時系統決定調度策略;
runtime: 運行時決定。
chunksize 是一個正整數

