MPI程序实现
作为比较“古老”的多核多线程并行技术,MPI已逐渐被CUDA等异构编程所取代。两者有本质区别,前者是一种库函数,面向编程语言本身,利用特定语句来指定CPU中各线程来分工完成任务。后者在GPU上实现,利用的主要是GPU具有大量运算单元的优势,从硬件本身来实现并行。若要从程序本身上实现并行,MPI实现并行计算则更加有优势。MPI是一种消息传递编程模型,目的是服务于进程间的通信。使用者需要一定的进程消息传递的空间想象能力。
参考书
高性能计算之并行编程技术——MPI并行程序设计(都志辉)
获取途径
1、MPICH是一种最重要的MPI实现,可以免费从http://www-unix.mcs.anl.gov/mpi/mpich 获得。
2、CHIMP是另一个免费的MPI实现,可以从ftp://ftp.epcc.ed.ac.uk/pub/packages/chimp/release/ 中获得。
3、从Ubuntu官网Ubuntu – Ubuntu Packages Search中下载。
并行程序实现
一个MPI并行程序主要由5部分构成,分别是头文件、相关变量声明、程序开始、程序体计算与通信、程序结束 。废话不多说,直接上实例,以下代码实现在5个进程中分别打印“Hello World"
program main
use mpi
character(MPI_MAX_PROCESSOR_NAME) :: processor_name
integer :: myid, numprocs, namelen, rc, iree
call MPI_INIT(ierr)
call MPI_COMM_RANK(MPI_COMM_WORLD, myid, ierr)
call MPI_COMM_SIZE(MPI_COMM_WORLD, numprocs, ierr)
call MPI_GET_PROCESSOR_NAME(processor_name, namelen, ierr)
write(*,10) myid, numprocs, processor_name
10 format('Hello World',I2,' of',I1,' on ',20A)
call MPI_FINALIZE(rc)
end program
程序第二行“use mpi”是头文件,我用的是Fortran95,用Fortran77的写法应该为“include”写法。第四行声明了需要使用的相关变量。第六行是程序开始,任何MPI程序写法都一样。第七至第十行是程序计算与通信部分,此程序还未涉及任何消息传递的过程。倒数第二行是程序结束命令。程序整体实现流程图如下。
程序实现流程图
输出结果:
Hello World 2 of 5 on nuaa-Super-server
Hello World 1 of 5 on nuaa-Super-server
Hello World 0 of 5 on nuaa-Super-server
Hello World 4 of 5 on nuaa-Super-server
Hello World 3 of 5 on nuaa-Super-server
由于每一次输出顺序未指定,因此四个线程同时进行输出。
消息发送、消息接受、返回状态
消息发送
MPI_SEND(buf,count,datatype,dest,tag,comm)
IN buf 发送缓冲区的起始地址(可选类型)
IN count 将发送的数据的个数(非负整数)
IN datatype 发送数据的数据类型(句柄)
IN dest 目的进程标识号(整型)
IN tag 消息标志(整型)
IN comm 通信域(句柄)
消息接收
MPI_RECV(buf,count,datatype,source,tag,comm,status)
OUT buf 接收缓冲区的起始地址(可选数据类型)
IN count 最多可接收的数据的个数(整型)
IN datatype 接收数据的数据类型(句柄)
IN source 发送数据的进程的进程标识号(整型)
IN tag 消息标识 (整型)
IN comm 本进程和发送进程所在的通信域(句柄)
返回状态
OUT status 返回状态 (状态类型)
例子
现在我们来利用消息的发送和接受实现一个并行计算,程序实现1+2+3+……+100,分别利用5个进程进行计算,进程1负责计算1+2+……+25;进程2负责计算26+27+……+50;进程3负责计算51+52+……+75;进程4负责计算76+77+……+100;进程0负责将进程1,2,3,4的计算结果汇总。需要注意的是,在并行计算中0进程往往不参与并行运算,它往往是多个运算进程的汇总。这就是我们常说的主从模式MPI设计。
program main
use mpi
implicit none
integer,parameter :: num=100, nccp=4
integer,save :: myid, numprocs, namelen, rc, ierr
integer,save :: istat(MPI_STATUS_SIZE)
integer(8) :: i, objval, temp_objval
call MPI_INIT(ierr)
call MPI_COMM_RANK(MPI_COMM_WORLD, myid, ierr)
call MPI_COMM_SIZE(MPI_COMM_WORLD, numprocs, ierr)
objval=0
temp_objval=0
if(myid.ne.0) then
do i=(myid-1)*(num/nccp)+1, myid*(num/nccp)
temp_objval=temp_objval+i
end do
write(*,10) myid, temp_objval
10 format('Process',I2,' calculation is :',I6)
call MPI_SEND(temp_objval, 1, MPI_INTEGER8, 0, mpi_tag,MPI_COMM_WORLD, ierr)
else
do i=1, nccp
call MPI_RECV(temp_objval, 1, MPI_INTEGER8, i,mpi_any_tag, MPI_COMM_WORLD, istat, ierr)
objval=temp_objval+objval
end do
write(*,20) objval
20 format('The final result is :',I6)
end if
call MPI_FINALIZE(rc)
end program main