一个简单的pingpong程序测试mpi消息通讯的开销及并行计算通讯启动时间测算

一个简单的pingpong程序测试mpi消息通讯的开销及并行计算通讯启动时间测算

一个简单的pingpong程序测试mpi消息通讯的开销

随着科技的进步,集群单节点计算能力的提高,似乎通讯开销成了并行计算中dominant,再提高计算能力对于并行的增益似乎效果不明显,限制性能的瓶颈从处理器计算能力上转移到通讯开销上。显然,此时设法降低MPI消息通讯带来的时间消耗,成为了当务之急。

因此,写了一个极其简单的pingpong并行程序来测试消息通讯带来的开销。

基本思想

所谓的pingpong,顾名思义就是找一个数据包不断地在两个节点之间丢来丢去,想打乒乓球一样。
我们在程序中定义两重循环,外重循环定义数据量大小,从1kb到100m,涨幅为100kb,内层循环对于每一固定大小的数据跑100个来回,最后取平均值,作为这个大小的数据的传输时间。最后,以数据量大小为横轴,时间为纵轴,plot一下。

准备工作

我们连接了机房的两台电脑,作为实验环境。主要设置了SSH免密登录和NFS共享目录。
一个简单的代码如下:

/**
 * pingpong程序,用于测试点点传送的速度
 */
//32位系统中,一个int占4B(sizeof(int)),1KB=250int,1M=250000int
//1M=1000kb,100M = 10e5kb
//使用N表示数据量大小为 N kb
//程序运行时,首先要把目录下的data.txt删除
#include <stdio.h>
#include <stdlib.h>
#include <mpi.h>
#include <string.h>

//#define N 4 //外层循环数据量大小改变,使用动态数组来实现
#define m 100 //定义pingpong的回合
#define debugflag 0

int main (int argc, char *argv[]) {


	int myrank,i,nprocs;
	double pingpongSize,aver_tcost,total_t,Ts[m];
	double st, et, time_cost;
	double N, Num_int;
	int *pingpong;
	char file_name[10];

	MPI_Init(&argc, &argv);//初始化MPI环境
	MPI_Comm_size(MPI_COMM_WORLD, &nprocs);//获取总进程数
	MPI_Comm_rank (MPI_COMM_WORLD, &myrank);//获取本地进程编号
	MPI_Status status;

	//int opposite_rank;  opposite_rank = (myrank == 0) ? (1) : (0);
	FILE *fp;
	sprintf(file_name,"data_from_rank%d.txt",myrank);
	fp=fopen(file_name,"w+");


//	for(N=1; N<=1e4; N=N+1e3) {
	for(N=1e-3; N<=1e5; N=N*2){
		total_t = 0.0;
		pingpong = (int*)malloc( N*1000);//开辟 N kb的内存
		Num_int = ( N*1000)/sizeof(MPI_INT);
		if(!pingpong) {
			printf("创建pingpong失败!\n");
			exit(1);
		}
	//	int pingpong[N] = {0};//随便赋值
	
		for(i=0;i<Num_int;i++)
		{
			pingpong[i] = 999;//赋值,并非必要
	//		printf("%d,%d",i,pingpong[i]);
		}
	
	//	pingpong[0] = 999;
		if(myrank==0)
		{
			printf("开始 %lfkb/1e5kb 数据传送……\n",N);
		}
		
		for(i=0; i<m; i++) {
			MPI_Barrier(MPI_COMM_WORLD);//同时开始计时
			st = MPI_Wtime();
			if(myrank==0) {
				MPI_Send (pingpong, Num_int, MPI_INT, 1, i, MPI_COMM_WORLD);
#if debugflag	
				printf("第%d回合:%d发送数据完成……\n",i+1,myrank);
#endif
			}
			if(myrank==1) {
				MPI_Recv (pingpong, Num_int, MPI_INT, 0, i, MPI_COMM_WORLD, &status);
				MPI_Send (pingpong, Num_int, MPI_INT, 0, i, MPI_COMM_WORLD);
#if debugflag				
				printf("第%d回合:%d接收和发送数据完成……\n",i+1,myrank);
#endif				
			}

			if(myrank==0) {
				MPI_Recv (pingpong, Num_int, MPI_INT, 1, i, MPI_COMM_WORLD, &status);
#if debugflag				
				printf("第%d回合:%d接收数据完成……\n",i+1,myrank);
				printf("第%d回合succeed! The time of cost is %lf\n\n",i+1,time_cost);
	//			printf("一个int占用:%ld B",sizeof(myrank));
#endif	
			}
			MPI_Barrier(MPI_COMM_WORLD);//保证进程1计算的时间也准确
			et = MPI_Wtime();
			time_cost = et-st;
			
			total_t = total_t+time_cost;
			Ts[i] = time_cost;

		}
		aver_tcost = total_t/m;
		
		
		pingpongSize = N/1e3;//单位换算成M
	//	printf("%lf个int的数据量大小为%lf M!\n",N,pingpongSize);

		fprintf(fp,"%lf,%lf\n",pingpongSize,aver_tcost);
		
		free(pingpong);
		
		if(myrank == 0){
			printf("%lfM 数据包发送接收回合完成……\n",pingpongSize);
			printf("%lfkb 的数据量的传送时间平均时间为: %lf \n\n",N,aver_tcost);
			
		}
		
		
	}
	fclose(fp);

	MPI_Finalize ();//结束MPI环境
	return 0;
}

实验和结果

将实验结果,画一下图,如下:

一个简单的pingpong程序测试mpi消息通讯的开销及并行计算通讯启动时间测算

从结果上可以看到,整体上看,通讯开销随着数据量大小呈现一个的关系,我们可以很容易地用线性函数去做线性fit,得到了T = 0.01711X+0.0004188这个这样一个关系,可靠程度:误差平方和为4.79e-6,均方根误差为0.0003263,确定系数约为1,说明这个拟合是很可靠的。
分析一波这个拟合结果,我们大概能知道每增加100m的数据,大概需要增加1.7s。所以,100M的数据,跑一百个回合,大概需要三分钟。

并行计算通讯启动时间测算

简述

我们知道,并行计算的消息传递不可能一上来就开始传数据,它是有一个启动时间的,就像游泳前的热身。那么这个启动时间是多少呢?又该怎么测算呢?
依然是pingpong的例子,只不过为了看出1kb之前的这个微小的数据变化段通讯时间情况,我修改了程序中数据量大小增加的情况,前面以double的形式从1B增加到1KB,往后,以5M为间隔,逐渐将数据量增加到100M。

数值实验

分别测定了双节点,和单节点的情况,结果如下。

双节点

首先使用两台节点跑了这个程序。
为了看到初始的那一小段的情况,我们将绘制的plot图(近似线性)在开始断不断地放到,最后发现,通讯时间在1kb左右的地方开始激增。
一个简单的pingpong程序测试mpi消息通讯的开销及并行计算通讯启动时间测算

从图上,我们可以看出,最开始的一段有一个抖动,这是意外。为了更好地体现这种通讯时间前面一小段几乎水平不变,后面呈线性增长的这种“拐角”情况,我们不妨来画一下双log图来呈现。为什么用双log图呢?很容易想到,如果趋势是线性的,那么双log后,展现出来还是线性的,如果是常数关系,那么双log后还是常数关系。log函数能使数据在展现上变得更加均匀,避免了“头重尾轻,细节模糊”的情况。双log函数,能够将细节的地方的展现和刻画出来,结果如下。
一个简单的pingpong程序测试mpi消息通讯的开销及并行计算通讯启动时间测算
可以看得出来,开始一段时间的通讯时间几乎没有改变,通讯启动时间大概为0.000165s,即0.165毫秒。

单节点

对于单节点,不涉及两台机器的通讯,我们重复了上面的测算过程,结果如下:
一个简单的pingpong程序测试mpi消息通讯的开销及并行计算通讯启动时间测算

上面直接plot的图之后再方法局部的图,发现有一个比较反常的点,估计是跑程序的时候,受到了别的因素的干扰。如果去掉这个离群点来考虑,它也是有这样一种“前平后翘”的“对勾”走势。我们再来看看双log图。

一个简单的pingpong程序测试mpi消息通讯的开销及并行计算通讯启动时间测算

双log图使数据呈现得更加“集中”,也使得原来离群得厉害的点变得那么不靠谱。从这里,因为开始时相对比较平坦(排除其他因素影响的情况下),我们假设开始那一段没有增长,姑且就可以把开始那段的值作为程序的启动开销。这里,可以看到,单击上,并行程序运行时,启动的开销大概为2e-6s,即0.002ms,基本上是两台机子通讯启动的百分之一。

由那些不正常抖动的情况来看,并行对于资源的要求还是很敏感的,比如说当你在跑程序时,有其他资源抢占了资源,就容易造成通讯开销的增加。