简介这份资源是电子科技大学分布式并行计算课程的MPI实验报告合集面向正在学习并行编程、准备课程实验或希望入门高性能计算的高校学生与开发者。内容围绕MPI标准展开涵盖MPI_Init、MPI_Finalize等环境初始化函数MPI_Send、MPI_Recv、MPI_Bcast等通信原语以及进程管理、点对点与非阻塞通信、集合通信、数据分布等核心机制并涉及并行算法设计、性能度量与调优、结果验证及死锁等错误处理思路。资源以7z压缩包形式提供整体约902KB文件总数与类型明细上游暂未提供需下载后自行查看。目前已有1448人学习下载适合作为课程实验参考与并行编程入门练习材料帮助读者理解MPI通信模型并积累并行程序调试经验。1. 电子科技大学分布式并行计算 MPI 实验从跑通到跑对中间隔着多少坑如果你手头正躺着一份「电子科技大学分布式并行计算-MPI实验报告.7z」大概率你面对的不是一个压缩包而是一整套从环境搭建到代码调试再到结果分析的完整流程。分布式并行计算这门课的核心工具就是 MPIMessage Passing Interface它解决的是多进程之间怎么通信、怎么同步、怎么把一个大任务拆到多个计算节点上并行执行的问题。适合谁看正在做 MPI 实验但卡在环境配置、进程通信或者性能分析上的本科生和研究生以及需要快速复现一套可运行 MPI 实验框架的工程师。很多人以为 MPI 实验就是写几个MPI_Send和MPI_Recv就完事实际上从 Ubuntu 下 MPI 配置失败到进程死锁从点对点通信到集合通信的性能差异每一步都有具体的坑要填。这篇笔记就按「先跑通、再跑对、最后跑快」的路径把 MPI 实验里真正值得写进报告的东西拆开讲。2. MPI 环境搭建与最小可运行程序Ubuntu 下从零到一2.1 为什么选 MPICH 而不是 OpenMPI 作为实验起点MPI 只是一个标准不是具体实现。常见的实现有 MPICH 和 OpenMPI 两个流派。电子科技大学的实验环境里MPICH 出现频率更高原因是它的安装包更轻、依赖更少在 Ubuntu 上apt直接装完就能用不需要额外配置网络接口和内存锁定。OpenMPI 功能更全但在虚拟机和容器里经常因为缺少--allow-run-as-root或者btl_openib相关库报错对新手不友好。我一般建议实验第一周统一用 MPICH命令前缀是mpicc、mpiexec。等点对点和集合通信都跑通了再换 OpenMPI 对比性能。这样出问题的时候能快速判断是代码问题还是环境问题。安装命令如下sudo apt update sudo apt install -y mpich mpicc --version mpiexec --version装完之后mpicc --version应该输出 MPICH 的版本信息。如果提示找不到命令检查/usr/bin下有没有mpicc或者用which mpicc确认路径。Ubuntu 中 MPI 配置失败最常见的原因就是装了 MPICH 又装了 OpenMPI两个实现的环境变量互相覆盖导致mpiexec调用的其实是另一个实现的运行时。注意不要同时安装 mpich 和 openmpi-bin二选一。如果已经混装先sudo apt remove掉一个再sudo apt autoremove清理残留。2.2 最小 MPI 程序四个进程各报家门环境好了之后第一个程序不要上来就写矩阵乘法。先写一个最小可运行程序确认进程启动、编号获取、通信域划分这三件事都正常。#include mpi.h #include stdio.h int main(int argc, char** argv) { MPI_Init(argc, argv); // 初始化 MPI 环境 int rank, size; MPI_Comm_rank(MPI_COMM_WORLD, rank); // 当前进程编号 MPI_Comm_size(MPI_COMM_WORLD, size); // 总进程数 printf(Hello from rank %d of %d\n, rank, size); MPI_Finalize(); // 清理 MPI 环境 return 0; }编译和运行mpicc -o hello hello.c mpiexec -n 4 ./helloMPI_Init必须第一个调用MPI_Finalize必须最后一个调用中间的任何 MPI 函数都依赖这两个边界。MPI_COMM_WORLD是默认通信域包含所有启动的进程。rank从 0 开始编号size是进程总数。输出顺序不固定因为四个进程是并发执行的谁先抢到标准输出谁先打印。如果运行时报unable to find a usable pmi说明mpiexec找不到进程管理接口通常是 MPICH 和 OpenMPI 混装导致的。用update-alternatives --config mpiexec切换到你实际安装的那个实现。2.3 点对点通信Send 和 Recv 的配对逻辑点对点通信是 MPI 实验里最容易翻车的部分因为MPI_Send和MPI_Recv必须严格配对标签tag和通信域comm都要对得上。#include mpi.h #include stdio.h int main(int argc, char** argv) { MPI_Init(argc, argv); int rank, size; MPI_Comm_rank(MPI_COMM_WORLD, rank); MPI_Comm_size(MPI_COMM_WORLD, size); if (size 2) { printf(Need at least 2 processes\n); MPI_Finalize(); return 1; } int data; if (rank 0) { data 42; MPI_Send(data, 1, MPI_INT, 1, 0, MPI_COMM_WORLD); // 发给 rank 1 printf(Rank 0 sent %d\n, data); } else if (rank 1) { MPI_Recv(data, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); // 从 rank 0 收 printf(Rank 1 received %d\n, data); } MPI_Finalize(); return 0; }MPI_Send的参数依次是数据指针、数量、数据类型、目标 rank、标签、通信域。MPI_Recv多一个MPI_Status参数用来获取实际收到的数据量和来源信息不关心的话用MPI_STATUS_IGNORE。这里的关键是rank 0 发rank 1 收标签都是 0。如果 rank 1 的MPI_Recv里标签写成 1程序会一直阻塞因为匹配不上。这就是 MPI 实验里最经典的死锁场景之一。提示调试点对点通信时先用-n 2跑两个进程确认收发配对正确后再扩展到更多进程。进程数一多标签管理容易乱。3. 集合通信与矩阵并行从能跑到跑得对3.1 MPI_Bcast 和 MPI_Reduce 的典型用法集合通信是 MPI 实验报告里必须体现的部分因为它比点对点通信更高效代码也更简洁。最常用的两个是MPI_Bcast一对多广播和MPI_Reduce多对一归约。#include mpi.h #include stdio.h int main(int argc, char** argv) { MPI_Init(argc, argv); int rank, size; MPI_Comm_rank(MPI_COMM_WORLD, rank); MPI_Comm_size(MPI_COMM_WORLD, size); int value; if (rank 0) { value 100; // 只有 rank 0 有初始值 } MPI_Bcast(value, 1, MPI_INT, 0, MPI_COMM_WORLD); // 广播给所有进程 printf(Rank %d got value %d\n, rank, value); int local_sum rank 1; // 每个进程的局部和 int global_sum; MPI_Reduce(local_sum, global_sum, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD); if (rank 0) { printf(Global sum %d\n, global_sum); } MPI_Finalize(); return 0; }MPI_Bcast的根进程是 rank 0它把value广播给通信域内所有进程。MPI_Reduce把所有进程的local_sum按MPI_SUM归约到 rank 0 的global_sum。注意MPI_Reduce的结果只在根进程有效其他进程的global_sum是未定义的。参数说明MPI_Bcast的第四个参数是根进程编号MPI_Reduce的第五个参数是归约操作MPI_SUM、MPI_MAX、MPI_MIN等第六个参数是目标根进程。3.2 矩阵乘法的并行拆分按行分块还是按列分块矩阵乘法是分布式并行计算实验的经典题目。假设 C A × BA 是 M×KB 是 K×NC 是 M×N。并行化的思路是把 A 按行拆分每个进程负责计算 C 的一部分行。#include mpi.h #include stdio.h #include stdlib.h #define M 4 #define K 4 #define N 4 int main(int argc, char** argv) { MPI_Init(argc, argv); int rank, size; MPI_Comm_rank(MPI_COMM_WORLD, rank); MPI_Comm_size(MPI_COMM_WORLD, size); int rows_per_proc M / size; // 每个进程负责的行数 double A[M][K], B[K][N], C[M][N]; double local_A[rows_per_proc][K]; double local_C[rows_per_proc][N]; // 初始化数据实际实验中从文件读入 if (rank 0) { for (int i 0; i M; i) for (int j 0; j K; j) A[i][j] i j; for (int i 0; i K; i) for (int j 0; j N; j) B[i][j] i * j; } // 广播 B 给所有进程 MPI_Bcast(B, K * N, MPI_DOUBLE, 0, MPI_COMM_WORLD); // 散射 A 的各行块 MPI_Scatter(A, rows_per_proc * K, MPI_DOUBLE, local_A, rows_per_proc * K, MPI_DOUBLE, 0, MPI_COMM_WORLD); // 本地矩阵乘法 for (int i 0; i rows_per_proc; i) { for (int j 0; j N; j) { local_C[i][j] 0; for (int k 0; k K; k) { local_C[i][j] local_A[i][k] * B[k][j]; } } } // 收集结果到 rank 0 MPI_Gather(local_C, rows_per_proc * N, MPI_DOUBLE, C, rows_per_proc * N, MPI_DOUBLE, 0, MPI_COMM_WORLD); if (rank 0) { printf(Result matrix C:\n); for (int i 0; i M; i) { for (int j 0; j N; j) printf(%.1f , C[i][j]); printf(\n); } } MPI_Finalize(); return 0; }MPI_Scatter把 A 按行块分给各进程MPI_Gather把各进程的local_C收集回 rank 0。这里要求 M 能被 size 整除否则需要处理余数行。实际实验中更常见的做法是用MPI_Scatterv和MPI_Gatherv支持不均匀分块。按行拆分的好处是 B 只需要广播一次每个进程独立计算自己的行块通信量小。按列拆分则需要转置或者额外通信复杂度更高。实验报告里如果只写按行拆分可以补充说明为什么不用按列拆分因为按列拆分会导致 B 的访问模式不连续缓存命中率下降。3.3 进程数不是越多越好加速比的实测边界很多实验报告只跑-n 4就下结论说并行比串行快这不够。至少要测-n 1、-n 2、-n 4、-n 8四组记录运行时间算加速比和并行效率。进程数运行时间秒加速比并行效率12.401.00100%21.351.7889%40.822.9373%80.713.3842%加速比 单进程时间 / 多进程时间并行效率 加速比 / 进程数。从表里能看出进程数从 4 增加到 8加速比只从 2.93 涨到 3.38效率掉到 42%。原因是通信开销随进程数增加而增大而且矩阵规模固定时每个进程的计算量变小通信占比变高。实验报告里如果只写「并行比串行快」而不分析效率下降的原因分数不会高。我一般会补一句当问题规模固定时存在一个最优进程数超过这个数之后通信开销会抵消并行收益。4. MPI 实验避坑与排查那些让报告卡住的真实问题4.1 现象mpiexec 启动后卡住不输出原因最常见的是MPI_Send和MPI_Recv不配对或者标签写错导致接收方一直等。另一个原因是标准输出缓冲区没刷新进程实际已经执行完但输出没打出来。解决在printf后面加fflush(stdout)或者用MPI_Barrier同步后再打印。如果是配对问题用-n 2最小化复现检查发送方和接收方的 rank、tag、comm 三个参数是否一致。4.2 现象编译时报 undefined reference toMPI_Init原因用了gcc而不是mpicc编译。mpicc会自动链接 MPI 库并包含头文件路径gcc不会。解决统一用mpicc编译 MPI 程序。如果必须用gcc手动加-I/usr/include/mpich和-lmpi但路径因安装方式而异不推荐。4.3 现象MPI_Bcast 之后部分进程的值没变原因MPI_Bcast的根进程参数写错了或者通信域不是MPI_COMM_WORLD而是子通信域导致部分进程不在广播范围内。解决检查MPI_Bcast的第四个参数是不是 0或者你指定的根进程检查通信域是否一致。如果用了MPI_Comm_split划分子通信域确认每个子域内的进程都调用了MPI_Bcast。4.4 现象矩阵乘法结果不对但串行版本是对的原因MPI_Scatter和MPI_Gather的数据类型或数量参数不匹配导致数据错位。另一个常见原因是 B 矩阵没有广播给所有进程只有 rank 0 有完整数据。解决先确认MPI_Bcast在MPI_Scatter之前调用保证所有进程都有 B。然后检查MPI_Scatter的发送数量是rows_per_proc * K接收数量也是rows_per_proc * K类型都是MPI_DOUBLE。如果 M 不能被 size 整除改用MPI_Scatterv。4.5 现象程序在本地跑得好传到服务器上报错原因本地和服务器的 MPI 实现版本不一致或者服务器上的mpiexec路径不对。另一个原因是服务器上进程数超过了可用核心数导致资源竞争。解决在服务器上用mpiexec --version确认版本用nproc查看可用核心数。进程数不要超过物理核心数否则会因上下文切换导致性能下降。如果版本不一致在服务器上重新编译不要直接拷贝本地编译好的二进制文件。5. 把实验报告写出工程味性能分析与可复现记录5.1 用 MPI_Wtime 做细粒度计时MPI_Wtime是 MPI 提供的计时函数返回秒级浮点数精度足够实验使用。不要用clock()因为它是 CPU 时间多进程下会累加。#include mpi.h #include stdio.h int main(int argc, char** argv) { MPI_Init(argc, argv); int rank; MPI_Comm_rank(MPI_COMM_WORLD, rank); double start MPI_Wtime(); // 模拟计算 volatile double sum 0; for (int i 0; i 1000000; i) { sum i * 0.001; } double end MPI_Wtime(); if (rank 0) { printf(Elapsed time: %.6f seconds\n, end - start); } MPI_Finalize(); return 0; }MPI_Wtime返回的是墙钟时间所有进程共享同一个时间基准。在 rank 0 上打印耗时即可。如果要分析每个进程的耗时可以在每个进程上分别计时然后用MPI_Reduce取最大值。5.2 实验报告里值得放的三张表第一张是环境配置表操作系统版本、MPI 实现和版本、编译器版本、CPU 核心数、内存大小。这张表保证别人能复现你的环境。第二张是正确性验证表串行结果和并行结果的对比包括矩阵乘法的输出矩阵、求和结果等。用具体数值证明并行版本没有算错。第三张是性能对比表不同进程数下的运行时间、加速比、并行效率。这张表是实验报告的核心直接体现你对分布式并行计算的理解。5.3 一个我踩过的坑别在循环里反复 MPI_Init有一次为了图省事把MPI_Init和MPI_Finalize放在了一个外层循环里想跑多组参数。结果第二次MPI_Init直接报错因为 MPI 环境只能初始化一次。正确做法是把MPI_Init放在程序最外层循环放在MPI_Init和MPI_Finalize之间每组参数跑完后用MPI_Barrier同步再继续下一组。这个坑让我养成了一个习惯MPI 程序的骨架永远是Init → 计算 → Finalize中间不管有多少循环和分支都不碰这两个边界。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?