MPI并行编程

之前曾简要介绍过openmp共享内存的并行编程。这种并行方式适合在大内存、多核心(cpu)的机器上应用。还有一种并行方式是分布式内存的并行,即利用多台机器(或一台机器上的多个线程,实际意义不大)分工合作,各个机器使用自己的内存存储数据并完成不同的任务。当然不同机器间还需要数据通信。MPI提供了这种分布的、基于消息传递的并行方式的实现规范和标准。当然在一个有多核心的多台机器构成的集群上,这两种并行方式可以混合进行。并行计算的方式基本上就这三种,曾在并行计算概述这篇文章里简要介绍过。这篇文章介绍下MPI并行编程的入门。

什么是MPI

stands for Message Passing Interface. The goal of , simply stated, is to develop a widely used standard for writing message-passing programs. As such the interface should establish a practical, portable, efficient, and flexible standard for message passing.”

MPI并不是一种编程语言,它只是一种消息传递的接口。它的主要功能就是提供数据在不同线程(机器)间进行传递的方法,相当于一个邮递员的作用,可以递送不同形式的包裹到不同的目的地。而整个程序的并行思想和方案其实完全是靠人脑来设计的。比如说,程序编写者也许需要两台机器来分工共同完成一件任务,具体每台机器做什么,一台机器需要从另一台机器得到什么或者给予什么后才能继续干下去,两台机器是否需要在适当的时候同步一下保持步调一致,等等…都是需要程序员自己事先分析设定清楚的。而MPI所做的,就是在程序提交后按照程序员的指导思想保持两台机器间的正常通信,包括数据传递,同步等待等等。

作为一种开源的标准,MPI可以有不同的具体实现。我所知道的有LAM/MPI库,以及MPICH库。以前用的是前者,后来改用了后者。个人觉得后者方便使用。

第一个MPI程序
program main
        use mpi
        integer totalid,myid,ierr
        call mpi_init(ierr)
        call mpi_comm_size(mpi_comm_world,totalid,ierr)
        call mpi_comm_rank(mpi_comm_world,myid,ierr)
        write (*,1),myid,totalid
1       format('Processor ',I1,' of ',I1)
        call mpi_finalize(ierr)
end program main

一个mpi规范的程序必须满足这种格式:

use mpi
call mpi_init(ierr)
[程序体]
call mpi_finalizet(ierr)

即先包含库mpi,然后以call mpi_init(ierr)初始化,中间是程序主体,最后是mpi_finalizet(ierr)结束。这种格式只在主程序中存在。子程序中如果要用到mpi库函数,只需先声明use mpi,然后调用相应库函数。

上面的程序中totalid是总的线程数,myid是当前线程的id。这个程序是让所有线程输出自己的id。如果用四个线程(机器)运行的话,其结果可能是:

Processor 0 of 4
Processor 2 of 4
Processor 1 of 4
Processor 3 of 4

注意其输出的id顺序并没有一定之规,而且每次执行出来的结果不尽相同。因为这是并行嘛,谁先谁就输出。

消息传递的简单例子

面对各种各样需要处理的实际情况,MPI库提供了丰富的接口供我们使用。具体在程序的编写中可能需要把MPI的接口程序列表放在身边参考。这里只拿最常用的发送,接受,收集,广播语句为例提供一种感觉。

program main
        use mpi
        integer myid,totalid,ierr
        integer i,A(2)
        integer,allocatable::B(:)
        call mpi_init(ierr)
        call mpi_comm_size(mpi_comm_world,totalid,ierr)
        call mpi_comm_rank(mpi_comm_world,myid,ierr)
       !每个线程都分配一个一维数组A。A(1)赋值0,A(2)赋值为当前线程的id。所以每个线程的A(2)依次为0,1,2,...
        A(1)=0
        A(2)=myid
       !每个线程(最后一个除外)调用mpi_send语句将自己的A(2)值传递给后一个线程。
        if(myid.ne.totalid-1) then
            call mpi_send(A(2),1,mpi_integer,myid+1,1,mpi_comm_world,ierr)
        end if
       !每个线程(第一个除外)调用mpi_recv语句接受前一个线程传来的数据,并存到A(1)中。
        if(myid.ne.0) then
            call mpi_recv(A(1),1,mpi_integer,myid-1,1,mpi_comm_world,stat,ierr)
        end if
       !每个线程执行加法,将A(1)的值加到A(2)上。
        A(2)=A(1)+A(2)
       ! 根据线程数目动态分配数组B。
        allocate(B(0:totalid-1))
      !利用mpi_gather语句收集各个线程上的A(2)值依次存放到0线程的B数组中。
        call mpi_gather(A(2),1,mpi_integer,B,1,mpi_integer,0,mpi_comm_world,ierr)
      !利用mpi_bcast语句将0线程的B数组广播到各个线程,各个线程都有了相同的数组B。
        call mpi_bcast(B,totalid,mpi_integer,0,mpi_comm_world,ierr)
      !最后一个线程打印出数组B。
        if(myid.eq.totalid-1) then
            print *,B
        end if
        call mpi_finalize(ierr)
end program main

这个程序干了如下一个没有多少实际意义的事:

  • 每个线程都分配一个一维数组A。A(1)赋值0,A(2)赋值为当前线程的id。所以每个线程的A(2)依次为0,1,2,…
  • 每个线程(最后一个除外)调用mpi_send语句将自己的A(2)值传递给后一个线程。
  • 每个线程(第一个除外)调用mpi_recv语句接受前一个线程传来的数据,并存到A(1)中。
  • 每个线程执行加法,将A(1)的值加到A(2)上。
  • 根据线程数目动态分配数组B。
  • 利用mpi_gather语句收集各个线程上的A(2)值依次存放到0线程的B数组中。
  • 利用mpi_bcast语句将0线程的B数组广播到各个线程,各个线程都有了相同的数组B。
  • 最后一个线程打印出数组B。

所以如果有5个线程执行这个程序,则输出为

0 1 3 5 7

程序的编译-Make file

下面是编译上面的程序的一个make file模板

SOURCE0 =
SOURCE =  main.f90
SOURCE +=
 
exec=sample.out
 
OBJ0 = $(patsubst %.f90,%.o,$(SOURCE0))
OBJ = $(patsubst %.f90,%.o,$(SOURCE))
OBJ1 = $(patsubst %.f90,%.o,$(SOURCE1))
 
F90 = ifort
FFLAGS = -O -g
FFLAGS_FPP =
FFLAGS_FPP += -fpp
 
FFLAGS_FPP += -I/usr/local/mpich-intel/include/f90base
LDFLAGS += -L/usr/local/mpich-intel/lib
FLIBS += -lmpichf90 -lmpich
 
%.o : %.F90
        $(F90) $(FFLAGS) $(MACRO) $(FFLAGS_FPP) -c -o $@ $<
%.o : %.f90
        $(F90) $(FFLAGS) $(MACRO) $(FFLAGS_FPP) -c -o $@ $<
 
all:    $(exec)
 
$(OBJ) : $(OBJ0)
 
$(exec):$(OBJ) $(OBJ1) $(OBJ0)
        $(F90) $(LDFLAGS) -o $@ $^ $(FLIBS)
 
etags:TAGS
TAGS:   $(SOURCE) $(SOURCE1) $(SOURCE0)
        etags $^
 
clean:
        rm -f *.od *.o *.mod *.dat
        rm -f $(exec) nohup.out

其中mpich的路径根据实际情况而定。

程序的提交

先编辑一个mf文件,在里面写上要使用个节点的IP。如

192.168.2.1
192.168.2.2
192.168.2.3
192.168.2.4

提交到后台:

nohup mpirun -nolocal -machinefile mf -np 4 ./sample.out &

其中-np用于指定使用的线程数目,会依次从mf文件的第一行开始使用。如果这里指定2,则只会用ip为192.168.2.1和192.168.2.2的两台机器上各开一个线程进行并行计算。
如果mf文件是这样的:

192.168.2.1
192.168.2.1
192.168.2.1
192.168.2.1

提交程序时指定-np为4的话,则会在192.168.2.1节点上开四个线程并行计算。

我学习mpi的资料

点击下载MPI.pdf

Related posts



35 comments. Leave a comment

  1. SHAN 说道:

    Easy to understand. Thanks~
    Nice job~

  2. FL 说道:

    文章全看不懂。我是不是地球人???

  3. su 说道:

    我这菜鸟 只好路过了啊

  4. 公子 说道:

    听说过这个。。

  5. Mucid 说道:

    计算机群运算啊! :-D

  6. 合肥电脑维修 说道:

    文章写的怪深奥的,不愧为科大的学生!加油!

  7. 学夫子 说道:

    哦听说过API ,哈哈

  8. 百度黑板报 说道:

    技术流啊 赞 gg被我点了 而且估计单价不低 哈哈

  9. Solo 说道:

    看不懂,杯具。

  10. 观尔腾 说道:

    总结的不错~点下AD~ :wink:

  11. 朵未 说道:

    呵呵,我就围观吧,看不懂哦。 :mrgreen:

  12. 雅岚 说道:

    :arrow: 看不懂丫看不懂。。。妖女岚飞走啦啦啦 :-P

  13. 竹下无为梦 说道:

    :-o 这次只能打酱油了,看不懂。

  14. 无赖 说道:

    网站又活了啊~ :mrgreen:

  15. 无赖 说道:

    技术含量太高,望尘莫及啊 8-O

  16. 飞猪 说道:

    挤不出合适的话来。。再占个板凳走吧~ :)

  17. 飞猪 说道:

    piggggggggggggggggggggg sofa

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

*

Comment

You may use these tags : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>