标题:MPI 简单上手 出处:Felix021 时间:Tue, 30 Jul 2013 18:47:31 +0000 作者:felix021 地址:https://www.felix021.com/blog/read.php?2123 内容: 简单地说,MPI 就是个并行计算框架,模型也很直接——就是多进程。和hadoop不同,它不提供计算任务的map和reduce,只提供了一套通信接口,需要程序员来完成这些任务;它也不提供冗余容错等机制,完全依赖于其下层的可靠性。但是因为把控制权几乎完全交给了程序员,所以有很大的灵活性,可以最大限度地榨取硬件性能。超级计算机上的运算任务,基本上都是使用MPI来开发的。 ~ 下载编译安装: 现在貌似一般都用MPICH,开源的MPI库,可以从这里获取: http://www.mpich.org/ ,现在的最新版本是3.0.4,编译安装过程可以参考安装包里的README的说明,基本步骤如下(万恶的configure): 引用 $ wget http://www.mpich.org/static/downloads/3.0.4/mpich-3.0.4.tar.gz $ tar zxf mpich-3.0.4.tar.gz $ cd mpich-3.0.4 $ mkdir ~/mpich $ ./configure --prefix=$HOME/mpich --disable-f77 --disable-fc 2>&1 | tee c.txt #我禁用了fortran的支持 $ make -j4 2>&1 | tee m.txt $ make install 2>&1 | tee i.txt $ echo 'export PATH=$PATH:~/mpich/bin' >> ~/.bashrc 下面给出三个例子,参考教程:http://wenku.baidu.com/view/ee8bf3390912a216147929f3.html (注:22页有BUG,它把 MPI_Comm_XXX 错写成了 MPI_Common_xxx //包括全大写版本,共四处),给出了MPI框架中最常用、最基础的6个API的例子。更复杂的API可以参考mpich的手册。这些例子只是简单地演示了MPI框架的使用;实际上在使用MPI开发并行计算的软件时,还需要考虑到很多方面的问题,这里就不展开说了(其实真相是我也不会-.-,有兴趣的话可以请教 @momodi 和 @dumbear 两位)。 1. 最简单的:Hello world 代码如下: hello.c #include #include int main(int argc, char *argv[]) { MPI_Init(&argc, &argv); //初始化MPI环境 printf("Hello world!\n"); MPI_Finalize(); //结束MPI环境 return 0; } 编译: $ mpicc -o hello hello.c 运行: $ mpiexec -n 4 ./hello Hello world! Hello world! Hello world! Hello world! 可以看到这里启动了4个进程。注意 -n 和 4 之间一定要有空格,否则会报错。 2. 进程间通信 MPI最基本的通信接口是 MPI_Send/MPI_Recv: #include #include int main(int argc, char *argv[]) { int myid, numprocs, source, msg_tag = 0; char msg[100]; MPI_Status status; MPI_Init(&argc, &argv); MPI_Comm_size(MPI_COMM_WORLD, &numprocs); //共启动几个进程 MPI_Comm_rank(MPI_COMM_WORLD, &myid); //当前进程的编号(0~n-1) printf("I'm %d of %d\n", myid, numprocs); if (myid != 0) { int len = sprintf(msg, "hello from %d", myid); MPI_Send(msg, len, MPI_CHAR, 0, msg_tag, MPI_COMM_WORLD); //向id=0的进程发送信息 } else { for (source = 1; source < numprocs; source++) { //从id=source的进程接受消息 MPI_Recv(msg, 100, MPI_CHAR, source, msg_tag, MPI_COMM_WORLD, &status); printf("from %d: %s\n", source, msg); } } MPI_Finalize(); return 0; } 编译运行: $ mpicc comm.c $ mpiexec -n 4 ./a.out I'm 0 of 4 from 1: hello from 1 from 2: hello from 2 I'm 1 of 4 I'm 2 of 4 from 3: hello from 3 I'm 3 of 4 3. 来个复杂点的:数数前1亿个自然数里有几个 雷劈数 代码后附,答案是97(真少),不过这不是重点,重点是MPI对硬件的利用率是怎样 :D 测试机器是 16核 AMD Opteron 6128HE @2GHz,32G内存 单进程(无MPI版本):56.9s 4进程:15.3s 8进程:7.85s 12进程:5.35s 考虑到16核跑满可能会受到其他进程的影响(性能不稳定,4.2~4.9s),这个数据就不列进来比较了。 可以看出来,在这个例子里,因为通信、同步只有在计算完之后才有那么一点点,所以在SMP架构下,耗费的时间基本上是跟进程数成反比的,说明MPI框架对硬件性能的利用率还是相当高的。 具体代码如下: #include #include int is_lp(long long x) { long long t = x * x, i = 10; while (i < t) { long long l = t / i, r = t % i; if (l + r == x) return 1; i *= 10; } return 0; } int main(int argc, char *argv[]) { int myid, numprocs, source; const int N = 100000000; MPI_Status status; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &myid); MPI_Comm_size(MPI_COMM_WORLD, &numprocs); printf("I'm %d of %d\n", myid, numprocs); int start = myid * (N / numprocs), stop = (myid + 1) * (N / numprocs); if (myid == numprocs - 1) stop = N; printf("start from %d to %d\n", start, stop); int ans = 0, i; for (i = start; i < stop; i++) if (is_lp(i)) ans += 1; printf("%d finished calculation with %d numbers\n", myid, ans); if (myid != 0) { MPI_Send(&ans, 1, MPI_INT, 0, 0, MPI_COMM_WORLD); } else { int tmp; for (source = 1; source < numprocs; source++) { MPI_Recv(&tmp, 1, MPI_INT, source, 0, MPI_COMM_WORLD, &status); printf("from %d: %d\n", source, tmp); ans += tmp; } printf("final ans: %d\n", ans); } MPI_Finalize(); return 0; } Generated by Bo-blog 2.1.0