并行系统学习之路(二) ---- MPI热身

本篇将介绍一个很简单的MPI程序。程序的要求是:

  • 程序将统计一个信息经过一个由处理器组成的环所需要的时间(比如说,讲一个消息从一个处理器节点发送至下一个处理器节点,直到最后一个节点发送返回第一个节点)
  • 统计不同大小的消息的发送时间(包括32B, 64B, 128B, …, 2M)
  • 统计不同处理器数量所需的时间(2个,4个,8个)
  • 作图表示不同消息大小对应的往返时间以及标准差

程序的核心代码如下:

for(msg_size = MIN_SIZE; msg_size <= MAX_SIZE; msg_size *= 2) {  
    // Construct send and data  
    send_data = malloc(msg_size * sizeof(*send_data));  
    recv_data = malloc(msg_size * sizeof(*recv_data));  
    for(int j = 0; j < ITERATION_TIMES + EXTRA_ITERATION; j++) {  
        if(rank == MASTER) {  
            // master node send data and receive data from last node to finish a loop  
            loop_times[j] = -MPI_Wtime();  
            MPI_Send(send_data, msg_size, MPI_CHAR, 1,  
             0, MPI_COMM_WORLD);  
            MPI_Recv(recv_data, msg_size, MPI_CHAR, numproc - 1,  
             0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);  
            loop_times[j] += MPI_Wtime();  
        }  
        else {  
            // other node receive data from the left node and send data to its right node  
            MPI_Recv(recv_data, msg_size, MPI_CHAR, (rank+numproc - 1) % numproc,  
             0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);  
            MPI_Send(send_data, msg_size, MPI_CHAR, (rank+1)%numproc,  
             0, MPI_COMM_WORLD);  
        }  
    }  
}  

本程序使用的消息收发方法为blocking的MPISend和MPIRecv,基本逻辑为:

  • 生成发送消息
  • 如果节点为MASTER节点,则记录起始时间并向下一个节点发送消息,待接受到来自最后一个节点的消息时记录结束时间
  • 其他节点将在接收来自前一个节点的消息之后向下一节点发送消息

主逻辑结束后我们用python的matplotlib库画出统计图如下:

我们从图中可以得出以下四点结论:

  • RTT随消息大小的增加而增加,因为当消息大小变大时,发送和接收消息所需要的时间会增加。
  • 当处理器数量增加时RTT会增加,因为处理器数量的增加意味着传输环的长度也增加了,RTT时间也会因此增加。
  • 对于相同大小的消息,所有的节点对传输消息的时间并不相同,而这种差异将会随着消息大小的增加变的可以忽略,以下为当我们取节点6和节点7时点对点消息发送时间的测试数据:

    Size: 32        Average: 1.101494e-05   Stddev: 1.412710e-05  
    Size: 64        Average: 6.222725e-06   Stddev: 8.691657e-07  
    Size: 128       Average: 6.628036e-06   Stddev: 4.744470e-07  
    Size: 256       Average: 8.368492e-06   Stddev: 6.348384e-07  
    Size: 512       Average: 9.393692e-06   Stddev: 3.234067e-07  
    Size: 1024      Average: 1.118183e-05   Stddev: 7.497876e-07  
    Size: 2048      Average: 1.463890e-05   Stddev: 8.272816e-07  
    Size: 4096      Average: 1.914501e-05   Stddev: 1.182758e-06  
    Size: 8192      Average: 2.329350e-05   Stddev: 1.077114e-06  
    Size: 16384     Average: 3.447533e-05   Stddev: 3.075877e-06  
    Size: 32768     Average: 4.868507e-05   Stddev: 2.696977e-06  
    Size: 65536     Average: 8.447170e-05   Stddev: 2.323939e-06  
    Size: 131072    Average: 1.497269e-04   Stddev: 2.056490e-06  
    Size: 262144    Average: 2.811909e-04   Stddev: 6.059022e-06  
    Size: 524288    Average: 5.399466e-04   Stddev: 2.198240e-06  
    Size: 1048576   Average: 1.061463e-03   Stddev: 3.065232e-06  
    Size: 2097152   Average: 2.106738e-03   Stddev: 1.004478e-05  
    

而当我们取节点6和75时,测试数据如下:

Size: 32		Average: 1.018047e-05	Stddev: 6.808134e-06  
Size: 64		Average: 5.602837e-06	Stddev: 6.419617e-07  
Size: 128		Average: 9.679794e-06	Stddev: 1.045579e-05  
Size: 256		Average: 1.194477e-05	Stddev: 1.145573e-05  
Size: 512		Average: 1.001358e-05	Stddev: 3.917617e-06  
Size: 1024		Average: 1.132488e-05	Stddev: 2.264977e-06  
Size: 2048		Average: 1.511574e-05	Stddev: 2.845484e-06  
Size: 4096		Average: 1.816750e-05	Stddev: 2.139931e-06  
Size: 8192		Average: 2.992153e-05	Stddev: 2.092794e-06  
Size: 16384		Average: 3.972054e-05	Stddev: 3.302248e-06  
Size: 32768		Average: 5.364418e-05	Stddev: 2.438406e-06  
Size: 65536		Average: 9.126663e-05	Stddev: 4.580864e-06  
Size: 131072		Average: 1.563311e-04	Stddev: 4.737336e-06  
Size: 262144		Average: 2.840281e-04	Stddev: 3.253431e-06  
Size: 524288		Average: 5.425692e-04	Stddev: 2.291921e-06  
Size: 1048576		Average: 1.065373e-03	Stddev: 4.348444e-06  
Size: 2097152		Average: 2.111387e-03	Stddev: 9.281682e-06  

很显然当消息大小比较小的时候节点6到75所需时间会大于6到7的时间,这也说明节点6与节点7的物理距离可能比节点6和75的距离要远。

  • 大小为32B的消息的RTT时间反而比大小为64B要长,这很可能是由于32B是第一个消息,而由于初始化或者其他原因,这个消息的发送时间相对会变长。测试发现当在正式计算之前发送10次模拟消息而不加入统计数据时,此差异将被消除。