第一二章
高性能計算概念
高性能計算(High performance computing,縮寫HPC) 指通
常使用很多處理器(作爲單個機器的一部分)或者某一集羣
中組織的幾臺計算機(作爲單個計算資源操作)的計算系統
和環境
性能衡量單位
floats
K | M | G | T | P | E |
---|---|---|---|---|---|
3 | 6 | 9 | 12 | 15 | 18 |
千 | 百萬 | 十億 | 萬億 | 千萬億 | 百億億 |
並行硬件
- Flynn 經典分類:SISD, SIMD, MISD, MIMD
- 內存結構分類:分佈式內存系統,共享內存系統
並行軟件
- 分佈式內存編程 MPI MapReduce
- 共享內存編程 Pthreads openmp
- GPU編程 CUDA OpenCL
第三章(MPI)
點對點通信函數
-
概念
-
MPI_Send()
MPI_Send(sendbuf,count, datatype, dest_proc,tag,comm); -
MPI_Recv()
MPI_Recv(recvbuf, count, datatype,src_proc.tag,comm,&status); -
問候程序
//Hello World的並行(mpi)
#include <stdio.h>
#include<string.h>
#include<mpi.h>
int main(int argc,char *argv[])
{
int rc,i;
int comm_sz,my_rank;
char message[100];
MPI_Status status;
int MAX_STRING = 100;
rc=MPI_Init(NULL,NULL);
MPI_Comm_size(MPI_COMM_WORLD,&comm_sz);
MPI_Comm_rank(MPI_COMM_WORLD,&my_rank);
//hello ,rc=0,MPI_SUCCESS=0(輸出四句)
//printf("hello ,rc=%d,MPI_SUCCESS=%d\n",rc,MPI_SUCCESS);
printf("hello ,I am %d of %d\n",my_rank,comm_sz);
if(my_rank!=0)
{
strcpy(message,"hello");
//MPI_Send(message,strlen(message)+1,MPI_CHAR,0,99,MPI_COMM_WORLD);
MPI_Send(message,MAX_STRING,MPI_CHAR,0,99,MPI_COMM_WORLD);
}
else
{
for(i=1;i<comm_sz;i++)
{
// MPI_Recv(message,100,MPI_CHAR,MPI_ANY_SOURCE,99,MPI_COMM_WORLD,MPI_STATUS_IGNORE);
MPI_Recv(message,100,MPI_CHAR,MPI_ANY_SOURCE,MPI_ANY_TAG,MPI_COMM_WORLD,&status);
printf("%s from %d\n",message,i);
}
}
MPI_Finalize();
return 0;
}
集合通信函數
概念
-
MPI_Reduce()
MPI_Reduce(send_buf,recv_buf,count,datatype,op,dest_proc,comm);
梯形積分 -
MPI_Scatter()
MPI_Scatter(send_buf_p,send_count,send_type,recv_buf_p,recv_count,recv_type,src_proc,comm) -
MPI_Bcast()
MPI_Bcast(data_p,count,datatype,src_proc,comm); -
MPI_Gather()
MPI_Gather(send_buf_p,send_count,send_type,recv_buf_p,recv_count,recv_type,dest_proc,comm)
梯形積分
- 梯形積分法,使其能夠在comm_sz無法被n整除的情況下,正確估計積分值(假設n>=comm_sz)
/*
* mpicc -lm -g -Wall -o ex2_tk2_1 ex2_tk2_1.c && mpiexec -n 5 ./ex2_tk2_1
*/
#include <stdio.h>
#include<math.h>
#include<mpi.h>
double f(double x)
{
return sin(x);
}
double Trap(double a,double b,double n,double h)
{
double estimate,x;
int i;
estimate=(f(a)+f(b))/2.0;
for(i=1;i<n;i++)
{
x=a+i*h;
estimate+=f(x);
}
return estimate*h;
}
void Get_input(int my_rank,int comm_sz,double* a_p,double* b_p,int* n_p)
{
int dest;
if(my_rank==0)
{
printf("Enter a,b,and n\n");
scanf("%lf %lf %d",a_p,b_p,n_p);
for(dest=1;dest<comm_sz;dest++)
{
MPI_Send(a_p,1,MPI_DOUBLE,dest,0,MPI_COMM_WORLD);
MPI_Send(b_p,1,MPI_DOUBLE,dest,0,MPI_COMM_WORLD);
MPI_Send(n_p,1,MPI_INT,dest,0,MPI_COMM_WORLD);
}
}
else
{
MPI_Recv(a_p,1,MPI_DOUBLE,MPI_ANY_SOURCE,MPI_ANY_TAG,MPI_COMM_WORLD,MPI_STATUS_IGNORE);
MPI_Recv(b_p,1,MPI_DOUBLE,MPI_ANY_SOURCE,MPI_ANY_TAG,MPI_COMM_WORLD,MPI_STATUS_IGNORE);
MPI_Recv(n_p,1,MPI_INT,MPI_ANY_SOURCE,MPI_ANY_TAG,MPI_COMM_WORLD,MPI_STATUS_IGNORE);
}
}
int main()
{
double a=0.0,b=3.0;
int n=2048,my_rank,comm_sz,local_n,source,q,r;
double h,local_int,total_int,local_b,local_a;
MPI_Init(NULL,NULL);
MPI_Comm_rank(MPI_COMM_WORLD,&my_rank);
MPI_Comm_size(MPI_COMM_WORLD,&comm_sz);
Get_input(my_rank,comm_sz,&a,&b,&n);
h=(b-a)/n;
q=n/comm_sz;
r=n%comm_sz;
if(my_rank<r)
{
local_n=q+1;
local_a=a+my_rank*local_n*h;
local_b=local_a+local_n*h;
}
else
{
local_n=q;
local_a=a+my_rank*local_n*h+r*h;
local_b=local_a+local_n*h;
}
local_int=Trap(local_a,local_b,n,h);
if(my_rank!=0)
{
MPI_Send(&local_int,1,MPI_DOUBLE,0,0,MPI_COMM_WORLD);
}
else
{
total_int=local_int;
for(source=1;source<comm_sz;source++)
{
MPI_Recv(&local_int,1,MPI_DOUBLE,source,0,MPI_COMM_WORLD,MPI_STATUS_IGNORE);
total_int+=local_int;
}
printf("n=%d,a=%.2f b=%.2f area=%f\n",n,a,b,total_int);
}
MPI_Finalize();
return 0;
}
- 廣播MPI_Bcast(輸入數據a,b,n)
- 規約MPI_Reduce/MPI_Allreduce(部分積分值求和)
/*
* mpicc -lm -g -Wall -o ex3_tk1_2 ex3_tk1_2.c && mpiexec -n 5 ./ex3_tk1_2
*/
#include <stdio.h>
#include<math.h>
#include<mpi.h>
double f(double x)
{
return sin(x);
}
double Trap(double a,double b,double n,double h)
{
double estimate,x;
int i;
estimate=(f(a)+f(b))/2.0;
for(i=1;i<n;i++)
{
x=a+i*h;
estimate+=f(x);
}
return estimate*h;
}
void Get_input1(int my_rank,int comm_sz,double* a_p,double* b_p,int* n_p)
{
if(my_rank==0)
{
printf("Enter a,b,and n\n");
scanf("%lf %lf %d",a_p,b_p,n_p);
}
MPI_Bcast(a_p,1,MPI_DOUBLE,0,MPI_COMM_WORLD);
MPI_Bcast(b_p,1,MPI_DOUBLE,0,MPI_COMM_WORLD);
MPI_Bcast(n_p,1,MPI_INT,0,MPI_COMM_WORLD);
}
int main()
{
double a=0.0,b=3.0;
int n=2048,my_rank,comm_sz,local_n;
double h,local_int,total_int,local_b,local_a;
MPI_Init(NULL,NULL);
MPI_Comm_rank(MPI_COMM_WORLD,&my_rank);
MPI_Comm_size(MPI_COMM_WORLD,&comm_sz);
Get_input1(my_rank,comm_sz,&a,&b,&n);
h=(b-a)/n;
local_n=n/comm_sz;
local_a=a+my_rank*local_n*h;
local_b=local_a+local_n*h;
local_int=Trap(local_a,local_b,n,h);
MPI_Reduce(&local_int, &total_int, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
//MPI_Allreduce(&local_int, &total_int, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
if(my_rank==0)
{
printf("n=%d,a=%.2f b=%.2f area=%f\n",n,a,b,total_int);
}
MPI_Finalize();
return 0;
}
並行計算和分佈式計算異同點
並行計算(paralel compunting) 一個程序通過多個任務緊密協作來解決某一個問題.
分佈式計算(disributed computing指一個程序需要與其它程序協作來解決某個問題。
並行計算與分佈式計算的區別
- 問題的來源與應用領域不同,並行計算主要來自於科學計算領域,而分佈式計算主要來自於商業領域
- 系統架構不同:並行計算主要是指在許多核或處理器上進行求解一個問題;而分佈式計算更強調的是跨系統、跨區域進行的協同工作來解決一個問題
- 分佈式系統強調的是資源的共享,和併發。
集合通信和點對點通信的不同
- 通信子中的所有進程必須調用相同的集合通信函數
- 參數必須相容
- 參數output_data_p只用在dest_process上,所有進程仍需要傳遞一個與output_data_p相對應的實際參數,即使它的值是NULL
- 點對點通信通過標籤和通信子來匹配,而集合通信通過通信子和調用的順序來匹配
派生數據類型
- 在MPI中,通過同時存儲數據項的類型及它們在內存中的
相對位置,派生數據類型可以用於表示內存中數據項的任
意集合。 - 主要思想:如果發送數據的函數知道數據項的類型及內存
中數據項集合的相對位置,就可以在數據項被髮送出去之
前在內存中將數據項聚集起來。 - 接收函數可以在數據項被接收後將數據項分發到它們在內
存中正確的目標地址上。 - 派生數據類型是由一系列的MPI基本數據類型和每個數
據類型的偏移所組成的
MPI整合多條消息數據的方式:
- 不同通信函數中的count參數
- 派生數據類型
- MPI_Pack/Unpack函數
性能評估
- 加速比
- 效率
線性加速比相當於並行效率p/p=1
第五章(openmp)
OpenMP的編譯運行(共享內存系統)
重點是理解和使用常見的指令、子句和函數的功能,能夠熟練應用
重點講解了幾個實例:梯形積分(多種並行形式) ,特別注意for循歡的並行
指令
- paralle指令:用在一個代碼段之前,表示這段代碼將被多個線程並行執行。
- for指令:使循環被多個線程並行執行;
- parallef for指令:循環的代碼被多個線程並行執行。
- atomic指令: 實現互斥訪問最快的方法。
- critical指令: 保護臨界區,實現互厲訪問。
- barrier指令 : 顯式路障,線程組中的線程都達到這個路障,才繼續往下執行。
子句
- num threads:用來指定執行之後的代碼塊的線程數目
- reduction:用來對一個或多個參數條目指定一個操作符
- default:用來允許用戶控制並行區域中變量的共享屬性
- private:用來聲明一個或多個變量是私有變量
- shared:用來聲明一個或多個變量是共享變量
- schedule:調度任務實現分配給線程任務的不同劃分方式
函數
-
omp_get_num_threads() // 返回當前並行區域中的活動線程1쐦.
-
omp_get_thread_num() //返回線程號。
-
omp_get_wtime() //計算OpenMP並行程序花費時間
-
omp_set num_threads() //設置線程的數量
-
void omp_ init_lock(omp_ lock_ t*lock) //初始化鎖
-
void omp destroy_lock(omp_ lock t*lock) //銷燬鎖
-
void omp_set_lock(omp_lock_t* lock) //嘗試獲得鎖
-
void omp_unset_lock(omp_ lock_ t* lock) /釋放鎖
openmp實例
臨界區
定義
臨界區指的是一個訪問共用資源(例如:共用設備或是共用存儲器)的程序片段,而這些共用資源又無法同時被多個線程訪問的特性。
如何保護
- critical指令
- 命名的critcal指令
- atomic指令
- 簡單鎖