預處理指令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 是一個正整數