MPI入门:实现并行pagerank算法
部分代码参考自:https://www.cnblogs.com/luyi07/p/11110658.html
首先对所需要的跳跃矩阵,PR矩阵进行初始化,代码如下,由0进程负责数据的加载和初始化,然后使用MPI_Scatter将原数据分块后分散到各个进程中去。分块操作由MPI_Scatter自行完成。MPI_Scatter函数接口如下:
int MPI_Scatter(
void* send_buf_p, /* IN */
int send_count, /* IN */
MPI_Datatype send_type, /* IN */
void* recv_buf_p, /* OUT */
int recv_count, /* IN */
MPI_Datatype recv_type, /* IN */
int src_proc, /* IN */
MPI_Comm comm /* IN */
)
其中send_count是为每个进程分配数据的大小,一般为data_count/comm_size,数据总数除以进程数。注意这里0进程使用MPI_Scatter进行分散的时候,0进程依然会得到“一块“数据的。其次,需要考虑分散后剩余数据的处理(可能存在均匀分配后剩余的数据)。参数src_proc确定了发送进程的编号。
#include<fstream>
#include<mpi.h>
#include<iostream>
using namespace std;
bool IniData(
double m[], //全局邻居矩阵
double local_m[], //存放分片的邻居矩阵
double pr[], //进程计算的PR值
double b[], //原始的PR值
int line, //分片大小
int size, //网页数
int my_rank, //进程编号
MPI_Comm comm //通信子
)
{
if(my_rank==0)
{
//初始化邻接矩阵、PR矩阵和初始PR矩阵
for(int i=0;i<size;i++){
for(int j=0;j<size;j++){
m[ i*size + j ] = 0;
}
b[i] = 1/(double)size;
pr[i] = 1/(double)size;
}
//从文件中读取数据
ifstream Input;
Input.open("data.txt");
int s,d;
while(Input >>s >>d)
{
m[ d*size + s] =1;
}
//变为转移矩阵
for(int i=0;i<size;i++)
{
int cout=0;
for(int j=0;j<size;j++)
{
if(m[j*size+i]==(double)1)
cout++;
}
if(cout>0)
{
for(int k=0;k<size;k++)
{
m[k*size+i]=m[k*size+i]/(double)cout;
}
}
}
//将分块的数据分发给各个进程,包括自己。
MPI_Scatter(m, line * size, MPI_DOUBLE, local_m, line * size, MPI_DOUBLE, 0, comm );
}
else
{
//接受转移矩阵
MPI_Scatter(m, line * size, MPI_DOUBLE, local_m, line * size, MPI_DOUBLE, 0, comm );
}
return true;
}
对于0进程调用MPI_Scatter为发送数据块给各个进程,对于其他进程,MPI_Scatter是接受0进程发送的数据块。另外MPI_Scatter是带有阻塞的,即接收方收到后才会进行后面的操作。
下面是并行计算部分的代码
int main(){
double alpha=0.85;
int my_rank;
int num_procs;
int size = 4;
double start, finish;
MPI_Init(NULL,NULL);
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
MPI_Comm_size(MPI_COMM_WORLD, &num_procs);
int line = size / num_procs;
cout<<" line = "<<line<<endl;
double m[size*size]; //整体的转移矩阵
double local_m[ line * size ]; //分发到各个进程中的分片矩阵信息
double b [size]; //初始概率矩阵,用于 计算陷阱问题
double pr[size]; //计算PR值
double local_pr[line];
//初始化个数据并发送给各进程
IniData(m,local_m,pr,b,line,size,my_rank,MPI_COMM_WORLD);
//所以Barrier是在点对点通信中才需要,这里不需要
//MPI_Barrier(MPI_COMM_WORLD);
for(int it=0;it<10;it++)
{
if( my_rank == 0 ){
//start = MPI_Wtime();
//广播每轮的pr值
MPI_Bcast(pr, size, MPI_DOUBLE, 0, MPI_COMM_WORLD);
//进程0计算自己分到的块
for(int i= 0; i< line;i++){
double temp = 0;
for(int j=0;j<size;j++){
temp += local_m[i*size+j] * pr[j];
}
local_pr[i] = temp;
}
//进程0负责计算分配之后剩下的
for(int i= num_procs *line; i< size;i++){
double temp = 0;
for(int j=0;j<size;j++){
temp += m[i*size+j] * pr[j];
}
pr[i] = temp;
}
//获取其他进程矩阵相乘后的结果
MPI_Gather( local_pr, line, MPI_DOUBLE, pr, line, MPI_DOUBLE, 0, MPI_COMM_WORLD );
//for(int i=0;i<size;i++)
// cout<<pr[i]<<endl;
//修正后的公式
for(int i=0;i<size;i++)
{
pr[i]=alpha*pr[i]+(1-alpha)*b[i];
// cout<<"pr"<<pr[i]<"\t";
}
//MPI_Bcast(pr,size,MPI_DOUBLE,0,MPI_COMM_WORLD);
if(it==9)
{
FILE *fp = fopen("result.txt","w");
for(int i=0;i<size;i++){
// for(int j=0;j<size;j++)
fprintf(fp,"%lf\t",pr[i]);
fputc('\n',fp);
}
fclose(fp);
}
//finish = MPI_Wtime();
//printf(" time: %lf s \n", finish - start );
}
else{
//double * buffera = new double [ line*size ];
//double * bufferc =new double [size];
//接受PR矩阵
MPI_Bcast(pr, size, MPI_DOUBLE, 0, MPI_COMM_WORLD);
// for(int i=0;i<size;i++)
// {
// cout<<"第"<<it<<"次 pr"<<pr[i]<<"\t";
// }
/*
cout<<" b:"<<endl;
for(int i=0;i<size;i++){
for(int j=0;j<size;j++){
cout<<b[i*size + j]<<",";
}
cout<<endl;
}
*/
for(int i= 0; i< line;i++){
double temp = 0;
for(int j=0;j<size;j++){
// cout<<"local_m"<<local_m[i*size+j]<<"pr"<<pr[j]<<endl;
temp += local_m[i*size+j] * pr[j];
}
local_pr[i] = temp;
//cout<<"local_pr"<<local_pr[i]<<"\t";
}
/*
cout<<" ans:"<<endl;
for(int i=0;i<line;i++){
for(int j=0;j<size;j++){
cout<<ans[i*size + j]<<",";
}
cout<<endl;
}
*/
MPI_Gather(local_pr, line, MPI_DOUBLE, pr, line, MPI_DOUBLE, 0, MPI_COMM_WORLD );
//delete [] buffera;
}
//MPI_Barrier(MPI_COMM_WORLD);
}
// delete [] m, b, local_m, pr;
MPI_Finalize();
return 0;
}
函数接口MPI_Scatter()是一对多的发送接口,而MPI_Gather()则是多对一的发送接口了,由各个进程计算完成后使用MPI_Gather()将计算结果发送给进程0做最后的汇总。MPI_Gather()的原型为:
int MPI_Gather(
void* send_buf_p, /* IN */
int send_count, /* IN */
MPI_Datatype send_type, /* IN */
void* recv_buf_p, /* OUT */
int recv_count, /* IN */
MPI_Datatype recv_type, /* IN */
int dest_proc, /* IN */
MPI_Comm comm /* IN */
)
MPI_Bcast()是用来进行组内广播的,在每次迭代开始之前,将上一轮计算结果广播给各个进程用来进行下一次计算。使用集合通信的方法来实现pagerank算法是同步并行计算,其次使用矩阵存储的话,比较占内存空间,所以需要做很多优化,之后会慢慢改进。