Blog

Learning by doing.


MPI学习笔记

May 2, 2013 • MPI • Tags: MPI FORTRAN

今天开始做一些MPI的小练习,练习取自一个不错的网站。 往下阅读前请将源程序下载好。。

Exercise 1: Hello, World!

这个练习给我们示范了三个函数的使用。

CALL MPI_INIT(ierror)                            ! 初始化MPI
CALL MPI_COMM_RANK(MPI_COMM_WORLD, rank, ierror) ! 各进程探测自己的Rank
CALL MPI_FINALIZE(ierror)                        ! 结束MPI

ierror

这个参数接收函数的返回,主要就是表示这个函数是否正确执行。

MPI_COMM_WORLD

每个进程组都在一个world里面,类似group的概念。

rank

每个进程都有一个进程号rank。

除了示范这三个函数,这个练习还要求我们学会MPI程序的编译和执行。

我只说一下目前主流的编译和执行流程:

mpif90 hello1.f -o hello1
mpirun -np 12 hello1                        # 使用12个进程运行

Exercise 2: Hello, Again!

这个练习给我们示范了三个函数的使用again!

CALL MPI_COMM_SIZE(MPI_COMM_WORLD, size, ierror)                          ! 探测全局大小
CALL MPI_SEND(message, 12, MPI_CHARACTER, i, tag, MPI_COMM_WORLD, ierror) ! 发送消息
CALL MPI_RECV(message, 12, MPI_CHARACTER, i, tag, MPI_COMM_WORLD, ierror) ! 接收消息

为什么MPI_SEND()MPI_RECV()参数里有个12呢?

数一下要发送的消息的字母数吧。 由于message = 'Hello World!'包含12个字符,后边数据类型又是MPI_CHARACTER,所以就算不知道每个参数是什么意思,组合起来一看也就猜出来了。

MPI_SEND()参数中的i又是什么意思?

这里循环i,应该是具有指代进程号的作用,查一下书,知道这个位置是”dest”,也就是destination,即目标进程的rank。

MPI_SEND()参数中tag又是什么?

这个猜不出来,只能查书了,书上说,顾名思义,就是个标记消息的tag!

MPI_RECV()参数中怎么是0?

自然表示从主进程中获取信息,所以这个0代表的是”source”,源头。

MPI_RECV()参数中为什么多了个status

这个有点复杂,查书知道是这么回事:当接收进程指定了”source”或者”tag”的通配符时,相应的信息会存储在这个变量中以备查询。

Exercise 3: Pi Calculation

终于开始真刀真枪的计算了! 可是实际上这个练习还是不需要你编程,但我们仍然可以好好阅读一下这个经典的程序,从中学到一些新知识。

比如,这个程序冒出来了三个之前没见过的函数:

CALL MPI_GET_PROCESSOR_NAME(processor_name, namelen, ierr)
CALL MPI_BCAST(n, 1, MPI_INTEGER, 0, MPI_COMM_WORLD, ierr)
CALL MPI_REDUCE(mypi, pi, 1, MPI_DOUBLE_PRECISION, MPI_SUM, 0, MPI_COMM_WORLD, ierr)

MPI_GET_PROCESSOR_NAME()

真的顾名思义了,但是这里顾名思义存在一定的歧义: processor到底是“处理器”还是“进程”还是“节点”的意思呢?

我们知道也可能不知道,一个节点可能由一个或多个处理器组成。 而现在我们运行的大型机必然是一个节点由多个处理器组成的,我们实际运行一下程序就会发现,所谓的processor_name返回的值是一样的,可见这里是“节点”名。

MPI_BCAST()

Broadcast,广播的意思,就是以“一对多”的方式,把消息发送给其他所有可接收消息的家伙!

标准定义是这样的: MPI_BCAST(buffer, count, datatype, root, comm)

所以,这里表示从进程0广播1个整型数据n到整个world。

MPI_REDUCE()

Reduce通常是“删除”的意思,但这里是“归纳”、“还原”的意思(reduction),我不确定这个函数其实是”reduction”还是”reductor”还是”reduce”的缩写,知道的同学告诉我一声哈。 但是我知道这个函数正统的名字叫作“规约”,表示将零散的数据收集起来进行特定的操作。

参数中的0表示进程0返回结果。

startwtime = MPI_WTIME()

不要放过任何一个新东西! 这里还冒出来了一个没见过的函数,差点漏看了。 wtime = wall time,可见是返回墙钟时间的,用来记录程序开跑(运行这一行代码)时的时间。

另外,值得注意的一个行间函数定义方式

f(a) = 4.d0 / (1.d0 + a*a)

不愧是公式翻译器。。。

下面来研究一下这里的并行算法

我们知道也可能不知道,积分f(x) = 4/(1 + x^2)可以得到pi的值。

为什么呢?如果是不定积分,结果是4arctan(x)。 如果取定积分,x从0到1,那么就是我们想要的结果pi!

而积分的数值计算其实就是取x在0到1中尽量多的值,算出尽量多个f(x),然后加起来就是pi了。 比如pi约等于f(0.1)+f(0.2)+…+f(0.9)+f(1.0),当然,这样计算得到的结果误差比较大。 所以程序中h = 1.0d0 / n,其中n = 10000把积分域分割成了10000份。 并且在累加时将x点向左偏移了0.5个h。

现在,我们来整理一下程序的思路: 首先,h = 0.000001。 进程0计算的第一个x点为0.5*0.000001 = 0.0000005, 计算的第二个x点为12.5*0.000001 = 0.0000125, 计算的第i个x点为(0.5 + 12*(i - 1)*0.000001); 进程1呢,计算的第一个x点为1.5*0.000001 = 0.0000005, 计算的第二个x点为13.5*0.000001 = 0.0000125, 计算的第i个x点为(1.5 + 12*(i - 1)*0.000001); 以此类推。。 这样,12个进程就分别负责了12组需要计算的x点,每个进程先把自己计算的那么多个结果累加,完了再规约到一起,得到接近pi真值的结果。 显然,分割地越细得到的结果应当越精确,而使用的进程越多,理论上的计算速度也应该越快(当然,这只是算法层面的,实际还受到通讯速度等影响)。

可见呢,计算pi这类问题本身是一个完美的并行算法问题。

归纳一下核心思想就是:

串行:Sum = A + B + C + D + E + F + ...

并行:Sum = (A + B) + (C + D) + (E + F) + ...

PERFECT!

Tags: MPI FORTRAN