Load Balancing & Termination
因为并行程序的运行速度主要取决于最慢的那个进程。所以保证每个进程的运行时间差不多是非常重要的,这也是负载平衡。
并行计算中的任务调度
静态调度,动态调度和混合调度
静态调度(static scheduling):是指在并行程序编译时,就决定每个任务的执行处理器及其执行时序,它经常用于任务图比较确定的情况下。比如笔记中的Eratosthenes筛法的并行算法就是静态调度。它的缺点有:1. 未执行程序前,非常多的情况下很难估计准确的执行时间,2. 不同的情况下,通信开销变化非常大。3. 一些问题的执行步数非常难以确定,比如Manderbrot set
动态调度(dynamic scheduling):在并行程序运行过程中,根据当前任务调度及系统执行情况,临时决定每个任务的执行处理器及起始执行时刻。
混合调度(hybrid scheduling):是介于静态调度和动态调度两者之间的调度方法,它在编译时先静态调度部分任务,而剩余部分则采用动态调度方法在系统运行过程中来给他们分配处理器。
动态负载平衡策略分为集中式(Centralized ),全分布式(Totally Distributed)和半分布式。
集中式策略:由一个主控节点收集全局负载信息,其他节点将他们的状态信息传送给主控节点,并由主控节点做出决策。如上图所示,下面以Mandelbrot Set为例子,说明一下集中式的调度方法。
Mandelbrot集合
,如果
则
属于Mandelbrot集合
程序:
- 给一系列复数
- 求这一系列的复数当$|Z_{k}| > 2$时的迭代次数
- 迭代次数最大值取256
用640*480的图片显示
的Mandelbrot集合
表示为一个
的矩阵,以0进程为主进程,将480列做数据分解,按动态调度的方法分给其余各从进程。每当一个从进程完成该列的计算,便返回该列的列号和运算结果,然后主线程分配新的列让该进程计算。
对于图片每个点对应的复数c,返回的迭代次数的函数为
//C++
int cal_pixel (complex<float> c) {
int count = 0; // number of iterations
int max= 256; // maximum iteration is 256
complex<float> z{0,0}; // initialize complex number z
do {
z=z*z+c;
count++; // update iteration counter
} while ((norm(z)< 4.0) && (count < max));
return count;
}
动态调度的算法
主线程
当前正在计算的进程数为count = 0;
需要被发送的行号row = 0;
总进程数为num_proc,为这num_proc个进程分配前num_proc+1行的矩阵数据,每个进程一行。
此时count值为num_proc,row值为num_proc
do{
//从从进程中得到计算结果
recv(&slave, &r, color, PANY , result_tag);
count--;//正在计算的进程数-1
if (row < num_row) { // 矩阵的行还没分完
send(row, Pslave , data_tag); //发送下一行
count++;//正在计算的进程数+1
row++;//下一个要分配的行号
}
else{//所有矩阵的行都处理完了
send(row, Pslave , terminate_tag); //发送终止信号
}
display(r, color,picture); // 把计算结果保存到picture里面
} while(count > 0)
最后将picture写入硬盘。
从线程
对于从进程P(i)
recv(&row, Pmaster , source_tag);//从主进程中获得要计算的行号
while (source_tag == data_tag) { //得到的信号是要继续计算
c.imag = min_imag + (row * scale_image);
for (x=0; x<640; x++) {
c.real = min_real + (x * scale_real);//矩阵行号和列号转化为复数c
color[x] = cal_pixel (c); // 计算迭代次数
}
send(i, row, color, Pmaster , result_tag); // 发送结果到主进程
recv(&row, Pmaster , source_tag);//从主进程得到新的行号或者终止信号
}
为了显示效果,我们将得到的图片取反,结果为
代码:https://github.com/chenlin0/Learning/tree/master/MPI/LoadingBalancing/
Mandelbrot_Set.cpp:源代码
Mandelbrot_Set:ubuntu下的可执行文件
Mandelbrot.txt:程序输出结果
Mandelbrot.png:输出结果取反后的图片
#include<iostream>
#include<complex>
#include"mpi.h"
#include<fstream>
using namespace std;
const int data_tag=0;
const int result_tag=1;
const int terminate_tag=2;
const int num_x=640;
const int num_y=480;
const float scale_x=1.0/160;
const float scale_y=1.0/160;
const complex<float> min_z={-2.0,-1.5};
struct Message{
int id;
int row;
int color[num_x];
};
int main(int argc,char *argv[]){
int cal_pixel (complex<float> c);
int id;//进程编号
int num_proc;//进程数
int count; // 正在进行的进程
int row; // 用来发送的矩阵行号
int picture[num_y][num_x];
MPI_Status stat;
Message result;//传递结果
complex<float> c;
MPI_Init(&argc,&argv);
MPI_Comm_rank(MPI_COMM_WORLD,&id);
MPI_Comm_size(MPI_COMM_WORLD,&num_proc);
MPI_Datatype Messagetype;
MPI_Datatype oldtype[2]={MPI_INT,MPI_INT};
int blockcount[2]={2,num_x};
MPI_Aint offset[2]={0,2*sizeof(int)};
MPI_Type_struct(2,blockcount,offset,oldtype,&Messagetype);
MPI_Type_commit(&Messagetype);
if(!id){//主进程
count = 0;
row = 0;
for (int i=1; i<num_proc; i++) { // 发送初始的行给各进程
MPI_Send(&row,1,MPI_INT,i,data_tag,MPI_COMM_WORLD);
count++;
row++;
}
do{
MPI_Recv(&result,1,Messagetype,MPI_ANY_SOURCE,result_tag,MPI_COMM_WORLD,&stat);//接受各进程的运算结果
// cout<<"Recv row:"<<result.row<<" id: "<<result.id<<" Tag:"<<stat.MPI_TAG<<endl;
--count;//在计算的进程数-1
for(int i=0;i<num_x;++i){//保存计算结果
picture[result.row][i]=result.color[i];
}
if(row<num_y){
//如果所有行没分配完
MPI_Send(&row,1,MPI_INT,stat.MPI_SOURCE,data_tag,MPI_COMM_WORLD);//刚刚结束计算的进程stat.MPI_SOURCE分配新任务
++count;//计算进程数+1
++row;//分配下一个行
}
else{//所有的数据已经分配完
MPI_Send(&row,1,MPI_INT,stat.MPI_SOURCE,terminate_tag,MPI_COMM_WORLD);//发送终止信号
}
}while(count>0);
ofstream f1("Mandelbrot.txt",ios::out);//输出结果
for(int i=0;i<num_y;++i){
for(int j=0;j<num_x;++j){
f1<<picture[i][j]<<" ";
}
f1<<endl;
}
f1.close();
}
else{
MPI_Recv(&row,1,MPI_INT,0,MPI_ANY_TAG,MPI_COMM_WORLD,&stat);
while(stat.MPI_TAG==data_tag){
c.imag(min_z.imag()+row*scale_y);
for(int i=0;i<num_x;++i){
c.real(min_z.real()+i*scale_x);
result.color[i]=cal_pixel(c);
}
//cout<<id<<":"<<endl;
result.id=id;
result.row=row;
MPI_Send(&result,1,Messagetype,0,result_tag,MPI_COMM_WORLD);
MPI_Recv(&row,1,MPI_INT,0,MPI_ANY_TAG,MPI_COMM_WORLD,&stat);
}
}
MPI_Finalize();//结束MPI环境
return 0;
}
int cal_pixel (complex<float> c) {
int count = 0; // number of iterations
int max= 256; // maximum iteration is 256
complex<float> z{0,0}; // initialize complex number z
do {
z=z*z+c;
count++; // update iteration counter
} while ((norm(z)< 4.0) && (count < max));
return count;
}
P.S. 军训期间基本没啥时间写这个,不过军训快结束了。我回去整理一下笔记。调度策略可能还要写几次,然后还有并行排序的算法。接下来就是偏微分方程的数值解法,当然都需要是并行的。现在也在学着搞异构计算,看情况异构计算和偏微分方程组并行数值解法交叉更吧。写作的目的有二,一个是鞭策我自己努力学习新的并行计算笔记。二是觉得中文网站上的资料实在太少了。
不过写到偏微分方程并行求数值解,应该也没什么人看了,异构计算应该也没啥人看,纯属自娱自乐吧。