多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

电子科技大学分布式并行计算MPI实验报告:从环境搭建到性能分析实战指南

电子科技大学分布式并行计算MPI实验报告:从环境搭建到性能分析实战指南 简介这份资源是电子科技大学分布式并行计算课程的MPI实验报告合集面向正在学习并行编程、准备课程实验或希望入门高性能计算的高校学生与开发者。内容围绕MPI标准展开涵盖点对点通信、集合通信、进程管理、数据分布、并行算法设计、性能分析与调优、结果验证及死锁等错误处理机制帮助读者把排序、搜索、矩阵运算等经典算法转化为并行实现并理解集群或超级计算机上的运行方式。压缩包为7z格式整体约902KB文件总数与类型明细上游未提供故不展开。目前已有1448人学习下载说明该实验报告在同类课程资料中具备一定参考热度。对于需要完成MPI实验、梳理并行程序设计模型或补充性能度量与调优思路的读者这份报告可作为对照实践、查漏补缺的学习材料。1. 电子科技大学分布式并行计算 MPI 实验报告从一份 .7z 到能跑通的并行程序如果你手里正好有一个名为「电子科技大学分布式并行计算-MPI实验报告.7z」的压缩包或者你正在为分布式并行计算这门课的实验发愁那这篇内容就是写给你的。它不是一个简单的实验报告模板而是一份能让你从零把 MPI 环境搭起来、把代码跑通、把结果测出来的实战笔记。分布式并行计算这门课的核心工具就是 MPIMessage Passing Interface而实验报告只是最终产出真正卡住大多数人的是环境配置、进程通信和性能分析这三座大山。我见过太多人卡在mpiexec报错上也见过有人把并行程序跑成了串行速度。这篇内容会按「环境搭建 → 点对点通信 → 集合通信 → 性能分析 → 避坑」的顺序推进每一步都有可复现的命令和代码。适合正在做 MPI 实验的本科生、需要快速上手 MPI 的开发者以及想搞懂并行程序到底怎么落地的人。2. 把 MPI 环境搭起来Windows 11 下 Microsoft MPI 与 WSL2 双路线2.1 为什么选 Microsoft MPI 而不是 MPICH在 Windows 上做 MPI 实验最常见的两个选择是 Microsoft MPIMS-MPI和 MPICH。MS-MPI 是微软官方维护的版本和 Visual Studio 集成度高安装包直接提供mpiexec.exe和mpiexec.h对于课程实验来说省去了大量编译配置的麻烦。MPICH 虽然跨平台更好但在 Windows 上编译安装的复杂度明显更高。我一般会建议课程实验优先用 MS-MPI因为实验报告里需要截图运行结果MS-MPI 在 Windows 终端下的输出更干净不容易出现乱码。如果你后续需要跑在 Linux 集群上再切到 OpenMPI 或 MPICH 也不迟MPI 标准是统一的API 层面几乎不需要改代码。2.2 Microsoft MPI v10.1.3 安装与验证安装 MS-MPI 需要两个包msmpisetup.exe和msmpisdk.msi。前者是运行时后者是开发包。安装完成后需要把 SDK 的Include和Lib目录加入 Visual Studio 的项目配置或者直接在命令行用cl.exe编译。下面是在 PowerShell 里验证安装是否成功的命令# 检查 mpiexec 是否在 PATH 中 mpiexec -help # 查看当前 MPI 版本信息 mpiexec -version # 运行一个最简单的 MPI 程序假设已编译为 hello.exe mpiexec -n 4 hello.exe-n 4表示启动 4 个 MPI 进程。如果看到 4 行输出说明环境正常。这里有个细节MS-MPI 默认不允许在管理员权限下运行如果你用管理员终端执行mpiexec可能会报错Unable to run mpiexec as administrator。解决办法是换普通用户终端或者加-wdir指定工作目录。2.3 WSL2 OpenMPI 路线适合想贴近 Linux 集群的人如果你不想折腾 Visual StudioWSL2 是更轻量的选择。在 Ubuntu 22.04 下安装 OpenMPI 只需要一条命令sudo apt update sudo apt install -y openmpi-bin openmpi-common libopenmpi-dev安装完成后用mpirun代替mpiexec参数基本一致。WSL2 的好处是你可以直接用gcc编译不需要配置 Visual Studio 的包含目录。但要注意WSL2 的网络栈和 Windows 主机是隔离的如果你需要跨主机通信WSL2 的默认 NAT 模式会带来额外配置成本。对于单机多进程实验WSL2 完全够用。提示MS-MPI 和 OpenMPI 的mpiexec参数有细微差别比如 MS-MPI 用-n指定进程数OpenMPI 也支持-n但 OpenMPI 还支持--oversubscribe来允许超过物理核心数的进程。实验报告里如果写了进程数超过 CPU 核心数记得加上这个参数。3. 点对点通信实验从 MPI_Send/Recv 到死锁排查3.1 最小可复现的 Send/Recv 代码点对点通信是 MPI 最基础的部分但也是最容易翻车的地方。下面这段代码实现了一个简单的环形通信每个进程把自己的 rank 发给下一个进程同时接收上一个进程发来的 rank。#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); int send_val rank; int recv_val -1; int next (rank 1) % size; int prev (rank - 1 size) % size; // 先发送再接收注意顺序 MPI_Send(send_val, 1, MPI_INT, next, 0, MPI_COMM_WORLD); MPI_Recv(recv_val, 1, MPI_INT, prev, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); printf(Rank %d received %d from rank %d\n, rank, recv_val, prev); MPI_Finalize(); return 0; }编译命令WSL2 下mpicc -o ring ring.c mpirun -n 4 ./ring逻辑说明每个进程先MPI_Send再MPI_Recv。在环形拓扑下如果所有进程都先发送理论上不会死锁因为每个进程都有一个独立的接收缓冲区。但如果把发送和接收的顺序反过来或者用阻塞式发送且消息很大就可能出现死锁。参数说明MPI_Send的第二个参数是数据个数第三个是数据类型第四个是目标 rank第五个是 tag第六个是通信域。MPI_Recv多了一个MPI_STATUS_IGNORE表示不关心发送方的状态信息。3.2 死锁的三种典型场景与排查方法第一种是「循环等待」进程 A 等 B 的消息B 等 A 的消息双方都不发送。第二种是「缓冲区不足」阻塞式MPI_Send在消息大于内部缓冲区时会阻塞直到接收方开始接收。第三种是「tag 不匹配」发送用的 tag 和接收用的 tag 不一致接收方永远等不到消息。排查死锁最直接的方法是加日志在每个MPI_Send和MPI_Recv前后打印 rank 和时间戳。如果程序卡住看最后一条日志停在哪个操作上。另一个技巧是用MPI_Iprobe非阻塞探测先检查有没有消息到达再决定是否接收。注意MS-MPI 的默认缓冲区大小和 OpenMPI 不同同样的代码在 Windows 上可能因为缓冲区小而阻塞在 Linux 上却正常。实验报告里如果写了「程序在 Windows 上卡住」大概率是这个原因。3.3 用非阻塞通信避免死锁把MPI_Send换成MPI_IsendMPI_Recv换成MPI_Irecv然后统一MPI_Waitall可以彻底避免循环等待导致的死锁。代码改动如下MPI_Request reqs[2]; MPI_Status stats[2]; MPI_Isend(send_val, 1, MPI_INT, next, 0, MPI_COMM_WORLD, reqs[0]); MPI_Irecv(recv_val, 1, MPI_INT, prev, 0, MPI_COMM_WORLD, reqs[1]); MPI_Waitall(2, reqs, stats);非阻塞通信的好处是发送和接收可以重叠提高并行效率。但要注意MPI_Isend返回后并不代表数据已经发出去只是请求已提交。必须调用MPI_Wait或MPI_Test来确认完成。实验报告里如果要做性能对比非阻塞版本通常比阻塞版本快 10% 到 30%尤其是在消息较大的情况下。4. 集合通信实验MPI_Bcast、MPI_Reduce 与矩阵乘法的并行化4.1 集合通信的语义与选型集合通信是 MPI 里最常用的功能包括MPI_Bcast、MPI_Reduce、MPI_Scatter、MPI_Gather、MPI_Allreduce等。它们的共同特点是涉及通信域内所有进程但语义各不相同。MPI_Bcast是把根进程的数据广播给所有进程MPI_Reduce是把所有进程的数据归约到根进程MPI_Allreduce则是归约后把结果再广播给所有进程。选型的关键在于你需要的是「一对多」「多对一」还是「多对多」。矩阵乘法并行化通常用MPI_Bcast广播矩阵 B然后用MPI_Scatter分发矩阵 A 的行块最后用MPI_Gather收集结果。4.2 矩阵乘法的 MPI 实现下面是一个简化的矩阵乘法并行实现假设矩阵维度 N 能被进程数整除#include mpi.h #include stdio.h #include stdlib.h #define N 512 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); int rows_per_proc N / size; double *A NULL, *B (double*)malloc(N * N * sizeof(double)); double *local_A (double*)malloc(rows_per_proc * N * sizeof(double)); double *local_C (double*)malloc(rows_per_proc * N * sizeof(double)); double *C NULL; if (rank 0) { A (double*)malloc(N * N * sizeof(double)); C (double*)malloc(N * N * sizeof(double)); // 初始化 A 和 B省略具体赋值 } // 广播矩阵 B 给所有进程 MPI_Bcast(B, N * N, MPI_DOUBLE, 0, MPI_COMM_WORLD); // 分发矩阵 A 的行块 MPI_Scatter(A, rows_per_proc * N, MPI_DOUBLE, local_A, rows_per_proc * N, 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 * N j] 0.0; for (int k 0; k N; k) { local_C[i * N j] local_A[i * N k] * B[k * N j]; } } } // 收集结果 MPI_Gather(local_C, rows_per_proc * N, MPI_DOUBLE, C, rows_per_proc * N, MPI_DOUBLE, 0, MPI_COMM_WORLD); if (rank 0) { // 输出或验证 C free(A); free(C); } free(B); free(local_A); free(local_C); MPI_Finalize(); return 0; }编译与运行mpicc -O2 -o matmul matmul.c mpirun -n 4 ./matmul逻辑说明MPI_Bcast把矩阵 B 从 rank 0 广播到所有进程这样每个进程都有完整的 B。MPI_Scatter把矩阵 A 按行分成size块每个进程拿到rows_per_proc行。本地计算完成后MPI_Gather把各进程的结果收集回 rank 0。参数说明MPI_Bcast的第二个参数是数据个数第三个是数据类型第四个是根进程 rank。MPI_Scatter的第二个参数是发送数据的总数第四个参数是每个进程接收的数据个数。注意MPI_Scatter的发送缓冲区只在根进程有效其他进程可以传NULL。4.3 性能对比不同进程数下的加速比在 4 核机器上跑 N512 的矩阵乘法串行版本大约需要 0.8 秒4 进程版本大约 0.25 秒加速比约 3.2。如果进程数增加到 8加速比可能只有 4.5因为通信开销开始占主导。实验报告里应该记录不同进程数下的运行时间并计算加速比和并行效率。加速比 串行时间 / 并行时间并行效率 加速比 / 进程数。一般来说进程数超过物理核心数后效率会明显下降。提示矩阵乘法实验里矩阵 B 的广播是通信瓶颈。如果 B 很大可以考虑用MPI_Scatter把 B 也分块或者用 Cannon 算法做二维分块。但课程实验通常不需要这么复杂一维分块足够展示并行效果。5. 避坑与常见问题MPI 实验里最容易翻车的 5 个点5.1 现象mpiexec报错「Unable to run mpiexec as administrator」原因MS-MPI 出于安全考虑禁止在管理员权限下运行。解决换普通用户终端或者用runas /user:普通用户名 cmd启动新终端。如果必须在管理员下运行可以加-wdir参数指定工作目录但这不是官方推荐做法。5.2 现象程序在MPI_Recv处卡住没有任何输出原因发送方和接收方的 tag 不匹配或者发送方根本没有发送。解决检查MPI_Send和MPI_Recv的 tag 参数是否一致检查发送方的目标 rank 是否正确。可以用MPI_Iprobe先探测消息是否存在再决定是否接收。5.3 现象并行程序比串行还慢原因通信开销大于计算收益或者进程数过多导致上下文切换频繁。解决减少通信量比如用MPI_Allreduce代替多次MPI_Reduce或者增大本地计算的数据块。实验报告里应该记录不同进程数下的时间找到最优进程数。5.4 现象MPI_Gather收集的结果顺序错乱原因MPI_Gather按 rank 顺序收集但如果发送方和接收方的数据个数不一致会导致缓冲区错位。解决确保每个进程发送的数据个数相同且接收缓冲区的总大小等于size * 每个进程的数据个数。如果数据个数不同用MPI_Gatherv。5.5 现象WSL2 下mpirun提示「not enough slots available」原因WSL2 默认只分配了部分 CPU 核心给虚拟机mpirun检测到的可用核心数少于请求的进程数。解决加--oversubscribe参数允许超订或者在 WSL2 配置文件里增加 CPU 核心数。命令示例mpirun --oversubscribe -n 8 ./program。6. 进阶技巧用 MPI_Wtime 做性能分析并写出一份有说服力的实验报告6.1 用 MPI_Wtime 测量通信与计算时间MPI_Wtime返回一个高精度的时间戳单位是秒。你可以在代码的关键位置插入时间戳计算各阶段耗时。下面是一个测量矩阵乘法各阶段时间的示例double start, end; start MPI_Wtime(); MPI_Bcast(B, N * N, MPI_DOUBLE, 0, MPI_COMM_WORLD); double bcast_time MPI_Wtime() - start; start MPI_Wtime(); MPI_Scatter(A, rows_per_proc * N, MPI_DOUBLE, local_A, rows_per_proc * N, MPI_DOUBLE, 0, MPI_COMM_WORLD); double scatter_time MPI_Wtime() - start; start MPI_Wtime(); // 本地计算 double compute_time MPI_Wtime() - start; start MPI_Wtime(); MPI_Gather(local_C, rows_per_proc * N, MPI_DOUBLE, C, rows_per_proc * N, MPI_DOUBLE, 0, MPI_COMM_WORLD); double gather_time MPI_Wtime() - start; if (rank 0) { printf(Bcast: %.4f s, Scatter: %.4f s, Compute: %.4f s, Gather: %.4f s\n, bcast_time, scatter_time, compute_time, gather_time); }逻辑说明MPI_Wtime在MPI_Init之后才能调用返回的是墙上时间不是 CPU 时间。参数说明无参数返回 double 类型。注意在 rank 0 上打印时间其他进程的时间可能因为负载不均衡而有差异。实验报告里应该记录多次运行的平均值并画出时间随进程数变化的曲线。6.2 实验报告里应该放哪些数据一份有说服力的 MPI 实验报告至少应该包含不同进程数下的运行时间表、加速比曲线、并行效率曲线、通信时间占比。下面是一个示例表格进程数运行时间(s)加速比并行效率通信时间占比10.821.00100%0%20.451.8291%8%40.253.2882%15%80.184.5657%32%从表里可以看出进程数从 1 增加到 4 时加速比接近线性但到 8 时效率明显下降。原因就是通信时间占比从 8% 涨到了 32%。实验报告里应该分析这个拐点并解释为什么继续增加进程数不再划算。6.3 一个容易被忽略的细节进程绑定与 CPU 亲和性在高性能计算场景下MPI 进程的 CPU 亲和性会显著影响性能。MS-MPI 和 OpenMPI 都支持通过参数绑定进程到特定核心。比如 OpenMPI 可以用--bind-to core把每个进程绑定到一个物理核心减少上下文切换。命令示例mpirun --bind-to core -n 4 ./matmul在 WSL2 下CPU 亲和性可能不生效因为 WSL2 的调度器由 Windows 主机控制。如果实验报告里要做性能对比建议在原生 Linux 或 Windows 下跑避免 WSL2 的调度干扰。我自己的习惯是先在 WSL2 下把代码调通再到实验室的 Linux 集群上跑最终数据。这样既省去了配置 Visual Studio 的时间又能拿到可靠的性能数据。注意MPI_Wtime的精度取决于操作系统Windows 下大约是微秒级Linux 下可以到纳秒级。如果测量的是很小的通信操作时间戳的误差可能比实际耗时还大。建议对同一操作重复多次取平均或者用MPI_Wtime测量大块操作的总时间。做 MPI 实验最深的教训是不要等到代码写完才去跑每写一个通信操作就编译运行一次确认没有死锁再继续。我见过太多人把整个程序写完结果卡在第一个MPI_Recv上回头排查要花几倍的时间。另外实验报告里的数据一定要自己跑出来不要抄别人的因为不同机器的核心数、内存带宽、MPI 版本都会影响结果抄来的数据一眼就能看出来。希望帮到你。本文还有配套的精品资源点击获取
返回列表