
简介这份来自电子科技大学的分布式并行计算MPI实验报告压缩包面向学习并行计算与MPI编程的高年级本科生或研究生可用于课程实验参考、期末复习或作为入门实践的对照材料。内容围绕MPI核心知识点展开涵盖MPI_Send/MPI_Recv等基础通信原语、进程初始化与身份获取、点对点与非阻塞通信、广播/规约等集合通信、数据在进程间的分布策略以及排序/矩阵运算等经典算法的并行化实例并涉及死锁规避、结果验证和性能调优方法能帮助读者从原理到代码实现建立起较完整的知识框架。压缩包体积约902KB文件数量暂未单独统计内部以实验报告文档为主已有1448人学习下载适合希望快速梳理MPI实验重点、获取可复用并行编程思路的学习者。1. 电子科技大学分布式并行计算-MPI实验报告这门课到底要你做什么每次带学生做电子科技大学分布式并行计算课程的MPI实验报告我观察到的第一反应不是读原理而是到处翻有没有现成代码。可真到了验收现场最紧张的时刻往往不是汇报PPT而是 mpirun 命令敲下去之后那台机器到底给不给面子。这份MPI实验报告真正要回答的问题其实很朴素你能不能把一个串行程序拆成多个进程用消息传递把数据搬来搬去最后交出一组可信的加速比数据。它的作用是充当一份可复现的交付物——环境怎么配、程序怎么改、数据怎么测、坑在哪里。适合正在写这门课报告的学生也适合需要帮别人调通环境、复核数据的助教和一线工程师。先别急着找代码先想清楚验收人会在你的报告里找什么。2. 跑通第一个MPI程序前先把环境这关过了如果你在这门课上翻车八成不是挂在MPI语法上而是挂在环境上。电子科技大学教学机上的实验环境一般是Linux终端和编译器都齐用OpenMPI或MPICH都能撑起这门课的后半程。我一般先用OpenMPI起步因为系统源里默认就有后续课程如果换MPICH再切换。先别急着上集群绝大多数MPI实验用一台服务器上的多个核就能完成节点之间的故事留到后面再讲。2.1 单机多进程就够了MPI实验环境的最小安装方案在 Ubuntu 类的系统上最小安装只需要两条命令。sudo apt-get update sudo apt-get install -y openmpi-bin libopenmpi-dev which mpicc mpirun --version第一行刷新软件源索引第二行把OpenMPI的运行时openmpi-bin和开发包libopenmpi-dev一起装好后者负责提供 mpi.h 头文件和编译封装。第三、第四行是验证which mpicc确认编译封装存在mpirun --version确认运行时可用输出里能看到版本号就说明环境基本通了。提示如果课程讲义里点名用MPICH就装 mpich 和 libmpich-dev不要两套混装。OpenMPI和MPICH的API高度相似但ABI不兼容混装会导致链接过程变成玄学报错位置毫无规律。想确认当前用的是哪一套可以执行mpicc --showme它会打印出实际传入编译器的include和链接路径。如果教学机上没有sudo权限可以走用户态安装从OpenMPI官网下载源码包后用./configure --prefix$HOME/openmpi配置安装路径再make -j4 make install。之后把export PATH$HOME/openmpi/bin:$PATH和export LD_LIBRARY_PATH$HOME/openmpi/lib:$LD_LIBRARY_PATH写进.bashrc。这个方案的好处是不污染系统环境缺点是第一次编译要花十来分钟期间如果报缺少依赖基本都是缺 libtool 或 flex装上再重新configure即可。这个最小环境已经能支撑起实验报告里所有的编程实验。开8个或16个进程都在这台机器上跑进程间通过共享内存通信不需要网络配置。2.2 集群选项什么时候真值得上多节点只有两种情况值得上多节点老师明确要求在hostfile里写多个节点或者你的问题规模大到单机内存装不下。对课程报告来说单机多进程是最稳的。下面是单机多进程与双机集群的对比。方案安装成本通信路径复现难度单机多进程一条apt命令共享内存低双机集群SSH免密、双方装库、程序同步TCP/InfiniBand高如果确实要上双机先把免密配好否则 mpirun 在启动阶段就会卡住。ssh-keygen -t rsa ssh-copy-id usernode2然后写hostfile每行一个节点slots表示这个节点最多能起几个进程。node1 slots4 node2 slots4这里有几个血泪经验。第一程序编译产物要复制到node2的相同路径下例如两边都有/home/user/mpi/pi否则node2上会报could not open ./pi。第二hostfile里的节点名要能解析不能只写IPSSH配置文件里的别名才算数。第三slots总和不能小于-np的总数否则启动时会报not enough slots。这些错误在单机模式下完全碰不到所以我的习惯是先把单机跑通再考虑集群。2.3 用mpirun启动最少参数与主机文件写法启动命令是这一行mpirun --hostfile hosts -np 4 ./pi 100000000如果只有单机可以省略--hostfilempirun -np 4 ./pi 100000000参数的含义--hostfile指定主机列表文件-np指定总进程数./pi是要运行的程序100000000是传给程序的参数。注意顺序mpirun自己的选项必须放在程序名之前而程序自己的参数放在程序名之后。一个常见的低级错误是把-np写到程序名后面那样它会被MPI进程当成程序的argv参数程序不会报错但进程数永远是默认的1。想让进程绑定到物理核可以加--bind-to core后面讲性能数据时会用到。环境就绪后写一个最小程序验证链路。保存下面的内容为 hello.c#include mpi.h #include stdio.h int main(int argc, char **argv) { int rank, size; MPI_Init(argc, argv); MPI_Comm_rank(MPI_COMM_WORLD, rank); MPI_Comm_size(MPI_COMM_WORLD, size); printf(rank %d of %d\n, rank, size); MPI_Finalize(); return 0; }编译并运行mpicc -o hello hello.c mpirun -np 4 ./hellompicc是MPI的编译封装内部自动调用gcc并链接MPI库。运行后能看到4行输出每行打印一个rank号。如果输出顺序乱序是正常的进程调度不保证顺序。到这里环境链路就通了接下来才适合碰实验本身。3. 从通信模式看懂报告里的三个核心实验MPI实验报告的实验部分翻来覆去就是围绕三类通信展开点对点、集合通信、自定义数据类型。把这三类背后的运行机制搞透比背API有用得多。我见过太多把代码跑通就算胜利的报告一答辩就露馅——评委随便问一句“你这里为什么先收后发”就答不上来。3.1 点对点通信MPI_Send/MPI_Recv的阻塞陷阱点对点通信是所有MPI程序的地基。一个最常见的翻车点是MPI_Send和MPI_Recv互相等待造成死锁。MPI_Send在标准模式下有一个系统缓冲区的“黑匣子”小数据会先被拷贝到缓冲区Send立即返回大数据则会等待接收端就绪。所以不能把代码正确性押在“缓冲区够大”这个假设上。// 偶数rank先发后收奇数rank先收后发避免双方同时等待 if (rank % 2 0) { MPI_Send(send_buf, 1, MPI_INT, rank 1, 0, MPI_COMM_WORLD); MPI_Recv(recv_buf, 1, MPI_INT, rank 1, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); } else { MPI_Recv(recv_buf, 1, MPI_INT, rank - 1, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); MPI_Send(send_buf, 1, MPI_INT, rank - 1, 0, MPI_COMM_WORLD); }这段代码的重点不是API拼写而是打破了“两个进程同时发、同时等”的对称性。偶数rank先发奇数rank先收形成一条不会互相卡住的边。如果两段都是先Send再Recv在小消息时侥幸能跑通一旦消息变大就会在某个进程数下死锁。MPI_Recv的最后一个参数是MPI_Status指针不关心接收信息时直接传MPI_STATUS_IGNORE省去定义一个变量的麻烦。验证点对点逻辑是否正确我一般会在两个rank之间做环形通信约定一个哨兵值。如果接收端拿到的数据与发送端不一致优先检查tag标签和rank配对是否写错。实验报告的代码里发送和接收应该成对出现能数清每一对消息这段代码才算合格。3.2 集合通信MPI_Bcast/MPI_Reduce的加速边界第二个实验通常是求pi或者矩阵乘法。用积分法求pi是最经典的考察点因为它把一个连续积分拆成多个进程各自算一段最后归约。这个过程同时用到了MPI_Bcast和MPI_Reduce。double local_sum 0.0; int chunk n / size; for (int i rank * chunk; i (rank 1) * chunk; i) { double x (i 0.5) / n; local_sum 4.0 / (1.0 x * x); } double pi 0.0; MPI_Reduce(local_sum, pi, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); if (rank 0) { printf(pi %.12f\n, pi / n); }MPI_Reduce把各进程的local_sum按MPI_SUM操作归约到root进程这里是指定的0号进程。count参数写1表示归约1个double元素不是1个字节。n / size要求n能被进程数整除否则最后一个进程分配的区间不完整计算结果就会有偏差。课程报告里如果遇到n不能被整除的情况我一般改成均匀分段 余数分给末尾进程的写法虽然代码长几行但答辩时能少一个问题。集合通信的加速边界在于MPI_Reduce本身的通信复杂度大约是O(log p)当每个进程只算几千个区间时通信时间就会盖过计算时间。报告里测多个规模时会看到一个清晰拐点——进程数超过某个值后加速比不再上升。把这个拐点写进分析比只贴一张“加速比持续上升”的图可信得多。3.3 自定义数据类型不是所有数据都能直接传第三个实验常见的是并行矩阵乘法或者结构体数据交换。一个典型错误是直接拿结构体指针当char*传然后告诉MPI“按字节数发就行”。MPI对类型匹配要求严格接收端的类型签名和发送端不一致轻则数据错位重则运行时崩溃。正确做法是用MPI_Type_create_struct声明自定义类型。typedef struct { double value; int row; int col; } Entry; MPI_Datatype entry_type; int block_lengths[3] {1, 1, 1}; MPI_Aint offsets[3]; MPI_Datatype types[3] {MPI_DOUBLE, MPI_INT, MPI_INT}; offsets[0] offsetof(Entry, value); offsets[1] offsetof(Entry, row); offsets[2] offsetof(Entry, col); MPI_Type_create_struct(3, block_lengths, offsets, types, entry_type); MPI_Type_commit(entry_type);offsetof计算每个字段相对结构体起始地址的偏移MPI_Type_create_struct告诉MPI这个自定义类型在内存里的真实布局MPI_Type_commit之后它才能像内置类型一样用在Send/Recv里。用完记得MPI_Type_free(entry_type)释放程序反复创建类型不释放长时间运行会积累内存泄漏。不commit就发消息很多实现会直接报Attempt to use an unvalidated datatype。相比MPI_Pack手动打包结构体类型的方式更符合课程想考察的类型安全性代码也更好读。4. 写出能放进报告的性能对比图表实验报告里最容易被挑刺的部分是性能数据。数据一旦被质疑重做会非常痛苦所以采集数据的方法要在一开始就定对。这一章把计时、计算和呈现方式一次说清。4.1 计时方式MPI_Wtime与墙钟时间的差异计时最好用MPI_Wtime而不是clock()。clock()返回的是CPU时间多进程时会叠加每个进程的CPU时间得出一个远大于真实耗时的荒谬数字。MPI_Wtime返回的是墙钟时间单位是秒精度到微秒级。double start MPI_Wtime(); // 实验体 double end MPI_Wtime(); printf(elapsed: %.6f s\n, end - start);采集策略上我的习惯是每个进程数跑3次取最小值而不是取平均值。服务器上的系统调度、磁盘抖动都会让某一次运行偏慢最小值最接近真实计算能力。报告中标注“每个数据点取3次运行的最小值”本身就是一种防质疑的写法。多节点场景下还要注意各节点的墙钟未必一致跨节点的MPI_Wtime对比要谨慎课程报告里一般单机就够了。4.2 加速比与效率怎么算才不被质疑加速比的定义是串行时间除以并行时间这里的串行时间必须用同一份代码以-np 1运行得到而不是引用讲义里的理论值。一个典型的非理想趋势长这样进程数墙钟时间秒加速比效率112.41.00100%26.61.8894%43.53.5488%82.15.9074%161.58.2752%效率 加速比 / 进程数。这张表的特点是加速比一直在涨但效率持续下降。说明并行开销的增长速度超过了计算收益。分析时别只写“可以看到加速比提升了”那等于没写。要写清哪一段接近线性、哪一段开始回落以及回落的原因——是通信占比上升、负载不均还是内存带宽触碰了上限。4.3 画图与分析报告评审看什么画图用折线图横轴进程数纵轴加速比画一条理想加速比参考线yx。理想线在哪里实际曲线偏离多少一眼就能看出来。柱状图适合对比两个独立方案不适合展示扩展趋势。评审通常会在三个位置找问题。第一串行时间是不是自己跑的——如果直接引用别人论文里的数字复现时对不上就露馅。第二有没有用绑核控制变量——不绑核时进程在不同核上迁移时间抖动会很大。第三数据能不能复现——所以跑完把原始日志以文本形式放进报告附录比在正文里强调“跑了很多次”更有说服力。5. 实验报告避坑环境、数据与常见翻车点这一章是我带实验时反复遇到的真实翻车记录。每条按现象、原因、解决的顺序写照着排查可以少走很多弯路。5.1 现象mpirun -np 4 ./pi 卡住不动命令敲下去之后光标一直闪烁既没输出也不退出。原因分两类一是hostfile里slots不够或者节点间互信没配好二是程序内部大数据量Send互相等待形成死锁。解决方法是先跑最小程序hello排除环境问题再加类似--mca btl_base_verbose 100的调试参数看通信层在等谁最后检查代码里有没有上面提到的对称Send死锁。5.2 现象每次运行的pi结果小数位都不一样这是分布式并行计算的正常现象不是bug。浮点数累加顺序不同舍入误差就不同。多进程的归约顺序与进程调度相关结果在小数点后几位浮动太正常了。解决方式是在报告中直接写一句“多进程累加顺序不同导致浮点误差在1e-5量级”这句话主动写在误差分析里反而加分藏着掖着被问出来才被动。5.3 现象4进程效率不错8进程效率断崖式下跌先看机器拓扑。很多服务器的8个逻辑核里有超线程共享一份计算单元和内存带宽进程数翻倍但算力没翻倍。解决方法是先用lstopo看物理核分布再用--bind-to core把进程绑到物理核上最后把问题规模调大重测。如果调大后效率回来了说明原来的数据规模太小通信开销主导了时间不是代码问题。5.4 现象实验室跑得好好的验收机器上直接崩动态库路径和编译器版本不一致是主因。你在自己机器上编译的产物拿到教学机上找不到对应版本的libmpi库就会出现启动即崩。解决方法是把库路径显式导出并让代码从源码现场编译export LD_LIBRARY_PATH/usr/lib/openmpi:$LD_LIBRARY_PATH mpicc -o pi pi.c更有用的是写一个run.sh把环境变量、编译、运行一次性固化下来。验收现场真正能带给人安全感的是一套能一键复现的流程而不是“我昨天跑过没问题”。5.5 现象报告里的数据“太好看了”以至于被质疑比如串行时间写的是别人论文里的最优值或者没算文件读取和初始化的时间。解决方式是串行时间必须用自己的程序实测并且统一计时口径计时从进入计算核心开始到结果输出前结束。把计时边界写进实验说明里数据再不好看也是可信的不好看。6. 让实验报告多拿分的验证技巧补充弱扩展实验大部分同学的报告只做“固定规模、逐步加进程”的强扩展实验也就是前面那张表的做法。想在数据部分拉开差距可以加一组弱扩展实验进程数翻倍时问题规模也翻倍让每个进程的负载保持不变。这组数据能从另一个角度证明你的并行程序具有可扩展性。6.1 弱扩展实验让效率曲线替你说话具体做法是用脚本控制问题规模随进程数等比放大。for p in 1 2 4 8; do n$((10000000 * p)) mpirun -np $p --bind-to core ./pi $n done单进程算1000万个区间2进程算2000万个4进程算4000万个。理想情况下每个进程的工作量不变墙钟时间应该大致持平。如果时间基本平缓说明程序的可扩展性成立如果时间明显上涨说明有负载不均或通信瓶颈。把这条效率曲线画在报告末尾比单纯堆强扩展数据更能体现理解深度。6.2 单进程验证最便宜的“后悔药”提交前还有一个零成本验证把代码用-np 1跑一遍和原本的串行结果对比。单进程运行就是天然调试器能让死锁、野指针、数组越界原形毕露。我在带师弟时发现他最高兴的时刻不是把pi算到第七位而是看到8个核全部满载运行。后来我拿到任何人的实验报告都会先问一句“单进程跑过吗、结果对得上吗”。如果你在写这份MPI实验报告也先把这个习惯建立起来。希望帮到你。本文还有配套的精品资源点击获取