
简介并行计算课程资源来自郑州大学计算机与人工智能学院适合学习并行计算理论、并行算法设计与高性能编程实践的高年级本科生或研究生。实验报告课程成绩90内容覆盖OpenMP、MPI等并行模型以及矩阵乘法、快速傅里叶变换、排序等常见并行算法设计图像绘制部分展示利用并行计算提升像素处理与渲染效率代码分析部分讲解性能瓶颈识别与并行度评估可帮助读者建立从算法到优化的完整思路。资源共1个文件夹以docx/doc报告、图片及代码文件为主压缩包约37.69MB便于对照实验步骤与图形结果进行复现。已有698人学习下载适合用于课程设计参考或并行计算实验复习。1. 实验报告的整体规划思路说实话刚开始拿到郑州大学并行计算这门课的实验要求时我第一反应是“这课能不能划水过”。但后来翻了一下课程大纲发现并行计算这门课如果不亲手把代码跑一遍、把性能曲线画出来光靠背书真的一点用都没有。最后我实验部分拿了90并不是因为我代码写得有多炫而是我把每个实验的思路、数据、图像和代码分析都弄得非常完整形成了一个闭环。这门课的核心就三个字并行化。让一段串行程序在多个处理器上同时跑跑完之后还能保证结果正确、速度还有提升。听起来简单但实际操作的时候坑特别多进程怎么分、通信怎么搞、负载怎么均衡、数据怎么切分每个环节都可能让性能不升反降。我的实验报告覆盖了几个核心方向MPI 点对点通信、MPI 集合通信、OpenMP 多线程并行、还有两者混合的并行模式。每个实验我都按照“理论原理 — 代码实现 — 实验结果 — 性能分析 — 图像绘制”的思路来写保证老师翻开实验报告的任何一个实验都能迅速看懂我做了什么、为什么这么做、效果如何。在正式写报告之前我专门花了一天时间把课程讲义里的重点公式和伪代码整理成了思维导图然后对照实验指导书逐条勾选知识点。这一步很关键因为并行计算的实验报告不同于普通编程作业老师更看重的是你对“加速比”“效率”“扩展性”这些核心指标的理解而不只是贴一段能跑通的代码。2. 实验环境与核心工具选型2.1 环境搭建细节我们学校实验中心提供的机器是集群式的计算节点每个节点是双路 Intel Xeon 处理器16 核 32 线程内存 64GB。操作系统是 CentOS 7编译器是 gcc 4.8.5MPI 实现用的是 MPICH 3.2OpenMP 则直接通过 gcc 的 -fopenmp 参数支持。我本地的开发环境是 Windows 10 WSL2里面装了 Ubuntu 20.04同样安装了 mpich 和 gcc。这里有个经验如果想在 Windows 上做 MPI 开发用 WSL 比用 Cygwin 省心得多Cygwin 的 mpich 版本有时会和 Windows 防火墙产生奇怪的兼容性问题而 WSL 2 原生支持 Linux 系统调用MPI 进程间通信几乎和在纯 Linux 环境中没有差别。查看 CPU 信息可以用下面这个命令lscpu输出里重点关注 Architecture、CPU(s)、Thread(s) per core、Core(s) per socket 和 Socket(s) 这几项它们决定了你后续做 OpenMP 线程绑定和 MPI 进程数量规划时的上限。比如双路 16 核的机器如果每核开 2 线程那最多就有 32 个硬件线程可用。2.2 为什么选择 MPI OpenMP 组合MPI 和 OpenMP 的选择不是随便拍的。MPI 适用于分布式内存模型每个进程有独立的地址空间进程之间通过消息传递来交换数据适合跨节点扩展但通信开销大、编程模型复杂。OpenMP 适用于共享内存模型多个线程共享同一个地址空间通过编译指令实现并行化编程门槛低但仅限于单节点内扩展受限于物理核心数量。所以我的实验设计里做了一个折中对于计算密集、数据量可控的实验用 OpenMP 实现代码量小容易验证正确性。对于大规模数据切分和节点间协作的实验用 MPI 实现训练消息传递的思维。最后再用混合模式每个节点上开 MPI 进程每个进程内部再开 OpenMP 线程模拟真实高性能计算场景中的两级并行。这种安排也符合行业内的主流做法。现在 TOP500 排名靠前的超算系统绝大多数采用的是 MPI OpenMP 的混合编程模式因为单纯使用 MPI 在单节点内的通信效率不高而单纯使用 OpenMP 又无法跨节点扩展。2.3 监视和性能分析工具除了编译器我实验过程中还用了几个非常关键的工具perfLinux 自带的性能分析工具可以统计 CPU 周期、缓存未命中率、分支预测失败次数等底层硬件事件。gprofGNU 的性能剖析工具编译时加 -pg 参数运行结束会生成 gmon.out 文件然后通过 gprof 分析函数级别的调用次数和耗时。GNUplot和Python Matplotlib绘制加速比曲线、效率曲线的工具。两个我都用过GNUplot 用起来更快Matplotlib 画出来更美观。实验报告里最终以 Matplotlib 为主。安装这些工具的命令很简单在 Ubuntu 下执行sudo apt install mpich gcc g gfortran gnuplot python3-pip pip3 install matplotlib numpy pandas我强烈建议你在第一次实验之前就把所有工具装好并跑通一个最小的 MPI 程序否则等真正开始做实验的时候再折腾环境时间会被严重浪费掉。3. 图像绘制的心得从数据到可视化3.1 图像在实验报告中的核心价值实验报告里如果没有图那几乎等于白写。老师看一份并行计算的实验报告最关心的就是你并行化的效果到底怎么样。而效果这个东西用一堆打印在终端上的数字来表达远不如一条曲线直观。加速比曲线、效率曲线、耗时对比柱状图这三类图在报告中出现频率最高也是拉开成绩差距的关键。我画图有个原则每张图必须有明确的横轴、纵轴图例标注清晰最好还配上误差线或趋势线。实验中的数据每次运行都会有波动尤其是 MPI 程序受网络延迟和系统调度影响比较大。我每类实验至少跑 5 次取平均值作为最终画图的依据标准差太大的数据点会重新跑一遍确认。3.2 用 Matplotlib 绘制加速比曲线下面是我在实验中画加速比曲线常用的代码模板我做了简化删去了和实验相关的业务逻辑只留下画图的核心部分import matplotlib.pyplot as plt import numpy as np # 实验数据进程数从1到8记录运行时间秒 processes [1, 2, 4, 8] times [12.34, 6.21, 3.25, 1.89] serial_time times[0] # 计算加速比 speedup [serial_time / t for t in times] plt.figure(figsize(8, 5)) plt.plot(processes, speedup, o-, colorblue, linewidth2, markersize8, label实测加速比) plt.plot(processes, processes, r--, label理想加速比) plt.xlabel(进程数, fontsize12) plt.ylabel(加速比, fontsize12) plt.title(MPI 并行加速比随进程数变化, fontsize14) plt.legend() plt.grid(alpha0.3) plt.xticks(processes) plt.tight_layout() plt.savefig(speedup.png, dpi150) plt.show()理想加速比就是一条 y x 的直线代表完美线性扩展。实测曲线越贴近理想直线说明并行化效果越好通信开销越小。如果曲线在某个进程数出现拐点甚至下降那就说明通信开销已经超过了计算收益这是分析实验时最重要的观察点。3.3 效率曲线怎么画更直观效率是加速比除以进程数反映的是并行资源利用率的指标。如果加速比是 5.7进程数是 8那效率就是 5.7 / 8 0.7125。效率低于 0.5 通常说明并行开销已经过大再增加进程数没有意义。画效率曲线的代码和加速比类似只需要多一步除法然后多画一条 100% 的水平参考线。效率曲线的价值在于当你在加速比图像中看到曲线依然上升无法直观判断是否应该继续增加进程数时效率曲线会清晰地告诉你收益递减的拐点在哪里。我实验里最经典的一张效率图是用 OpenMP 计算矩阵乘法时的表现4 线程时效率约 92%8 线程时小幅下降至 84%16 线程时骤降到 55%。这个结果合理解释了为什么并非线程越多越好——线程增多同步成本和访存竞争急剧增加在计算量不够大的时候收益甚至无法覆盖开销。3.4 绘图时的坑位列表画图看似简单实际操作中我踩过不少坑整理成表格分享给大家坑现象解决方法横轴数据密度不均曲线形状失真使用 np.linspace 或 logscale 重新采样Y 轴被异常值拉长正常数据挤在底部删除异常点或设置 ylim中文乱码标签显示为方框在代码开头设置 rcParams 中文字体比如 SimHei或 微软雅黑曲线过于平滑缺少波动被质疑数据真实性保留实测数据点不加过多插值颜色单调无区分为黑白打印后看不清用不同线型折线、点线、虚线搭配颜色提示报告中插入的图像分辨率至少 150dpi建议 300dpi否则打印出来边缘发虚给老师的体验感会直接拉低。4. 代码分析的思路与工具链方法4.1 代码分析到底分析什么很多同学的实验报告里贴了一大段代码然后写一句“图一为实验结果”这就完事了。这种写法注定拿不了高分。代码分析的核心不是贴代码而是分析代码背后体现的并行思想数据是怎么划分的计算任务是怎么分配的进程或线程之间是怎么同步的通信和计算有没有重叠负载是否均衡我每份实验报告在代码部分都按照这个顺序来写算法流程 - 关键代码段 - 复杂度分析 - 性能瓶颈分析。用尽量少的代码说明问题不贴整段只贴核心片段然后对每一行关键操作做注释说明。4.2 MPI 点对点通信的坑MPI 中最基础也最容易出错的是点对点通信。我实验里做过一个简单的例子进程 0 生成一个数组分发给其他进程各进程求和再汇总给进程 0。#include mpi.h #include stdio.h #include stdlib.h int main(int argc, char* argv[]) { int rank, size, partial_sum 0, total_sum 0; int data[128]; MPI_Init(argc, argv); MPI_Comm_rank(MPI_COMM_WORLD, rank); MPI_Comm_size(MPI_COMM_WORLD, size); if (rank 0) { for (int i 0; i 128; i) data[i] 1; } // 每个进程只负责 128/size 个元素 int chunk 128 / size; int* sub_data (int*)malloc(sizeof(int) * chunk); // 分发数据 MPI_Scatter(data, chunk, MPI_INT, sub_data, chunk, MPI_INT, 0, MPI_COMM_WORLD); for (int i 0; i chunk; i) partial_sum sub_data[i]; // 收集部分和 MPI_Reduce(partial_sum, total_sum, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD); if (rank 0) { printf(Total sum %d\n, total_sum); } MPI_Finalize(); return 0; }这段代码看起来没什么问题但最容易踩坑的地方是128 / size。如果 size 不能整除 128比如用 3 个进程跑那每个进程分的 chunk 是 42但 3 * 42 126还有 2 个元素没人处理。正确的做法是用向上取整或者最后一个进程处理剩余的部分再或者用MPI_Scatterv来实现不均匀切分。注意在实际大型项目中数据量通常不是进程数的整数倍MPI_Scatterv几乎是必学的接口建议每个学生都练一遍。4.3 gprof 的性能瓶颈定位法我实验里有一段代码用来对比串行和并行版本的大规模 SIMD 操作纯看代码很难定位瓶颈所以我用 gprof 做了一个函数级的热点分析。步骤很简单gcc -pg -o program program.c -fopenmp ./program gprof program gmon.out analysis.txt生成的 analysis.txt 里显示了每个函数的调用次数、自身耗时和累计耗时。我一眼就看到一个名为 matrix_multiply 的函数占用了 87.3% 的执行时间而它内部的并行区只覆盖了最外层循环内层循环依然串行执行导致并行度不够。发现问题之后我把#pragma omp parallel for从外层循环移到了内层同时对内层循环加入了reduction(:sum)子句来避免数据竞争。修改后程序运行时间从 8.42 秒降到了 2.15 秒加速比从原来的 1.4 提升到了 5.6。这个案例在报告里成为了代码分析部分的亮点因为它展示了完整的“发现问题-分析原因-优化验证”链路而这恰恰是老师在评分标准中明确看重的能力。4.4 并行程序正确性验证性能分析做得再漂亮结果算错了也白搭。并行程序的正确性验证是代码分析中不可跳过的一环。我习惯用以下三种方法叠加验证与串行版本的结果比对在数据量较小的情况下将并行程序输出与原始串行程序输出做 diff。使用随机数据做多轮测试每次生成不同的随机矩阵或数组验证结果一致性。使用浮点数容差判断由于浮点运算顺序不同会导致微小误差绝对相等比较不现实需要设置阈值比如 1e-6。在 OpenMP 实验中我因为忽略了对共享变量的保护导致在线程数增多时结果出现偏差。多次排查后确认是变量sum被多个线程同时写入导致数据竞争。解决办法是在累加前加入#pragma omp atomic或者直接使用reduction子句后者代码更简洁、性能也更好。5. 常见实验问题与避坑技巧5.1 进程卡死排查实验过程中最让人抓狂的问题就是 MPI 程序跑起来之后卡住不动终端像死了一样。这种问题绝大多数是死锁造成的进程 A 发送了一条消息给进程 B但 B 在等待接收来自 C 的消息而 C 又在等待 A 的消息形成了一个环形等待链。排查死锁我有一套固定的方法先加上超时机制例如使用MPI_Isend/MPI_Irecv对通信状态做轮询。检查所有MPI_Send和MPI_Recv的匹配关系。一个MPI_Send必须有对应的MPI_Recv且标签、数据类型、数量要完全匹配。使用MPI_Barrier辅助打印各进程运行到哪一行定位卡死位置的代码块。如果自己实在找不到就减少进程数到 2 个用最小规模复现再逐步增加。5.2 负载不均衡的调整有一回做 mandaebrot 集合并行计算实验我用静止的矩阵分块方式把区域平均分配给各进程。结果发现速度几乎和串行一样因为曼德勃罗集合的收敛计算量在图像不同区域差异极大有的区域几乎不需要迭代有的区域要迭代几千次。这就是典型的负载不均衡问题。解决办法是改用动态任务分配把整个区域划分成很多个小块比进程数多几倍然后哪个进程算完当前任务就去任务队列领取下一个任务。这个过程在 MPI 中可以用主从模式实现主进程充当任务分配器从进程不断请求任务。改用动态分配后加速比从 1.2 直接提升到了 4.3效果立竿见影。5.3 OpenMP 线程绑定OpenMP 实验中遇到过一个非常隐蔽的问题在单节点上跑双重循环两个线程可能被操作系统调度到同一个物理核上导致性能不升反降。解决办法是设置线程绑定环境变量export OMP_PROC_BINDtrue export OMP_PLACEScoresOMP_PLACEScores表示每个线程绑定一个物理核心避免两个线程挤在同一个核的同一个超线程上。设置之后8 线程矩阵乘法的运行时间从 1.1 秒降到了 0.6 秒左右提升显著。提示在新版的 gcc 里OMP_PROC_BINDtrue默认是关闭的需要显式设置。batch 调度系统里如果忘了配置性能会白白浪费掉。5.4 实验数据整理的独家表格几次实验下来我形成了一套自己的数据记录模板每个实验都跑多组数据记录关键指标方便后续画像和横向对比。模板分享如下实验名称串行时间(s)并行时间(s)加速比效率通信开销占比(%)MPI: 数组求和(4进程)12.343.783.2681.5%12.4%OpenMP: 矩阵乘法(8线程)8.421.157.3291.5%3.2%混合模式: 2进程x4线程15.682.316.7984.9%15.1%这张表在报告里可以直观地展示不同并行方式的性能差异比单纯的文字描述有说服力得多。6. 成绩90的经验沉淀最后再说点实在的。并行计算这门课实验想拿高分我个人觉得核心就三点原理要吃透、数据要真实、分析要闭环。原理吃透的意思是你写的每一个MPI_Send背后要清楚它在底层做了什么——数据被拷贝到缓冲区、通过网络传送、再被拷贝到接收端这个过程是耗时的所以要尽量减少通信次数、增加单次通信的数据量。同理你写的每一个#pragma omp parallel要清楚线程之间怎么共享变量、怎么处理临界区、怎么避免伪共享。数据要真实的意思是不要为了让曲线好看而篡改数据。我见过有同学在跑矩阵乘法并行时串行版本故意加上大段的sleep把串行时间拉长这样加速比就显得特别好看。这种操作在答辩时很容易被识破——老师只要问一句“你的串行时间为什么这么大”就露馅了。分析要闭环的意思是每个实验不仅要有“跑出来了”“结果正确了”还要有“为什么结果是这样”“有没有什么问题”“怎么优化”。比如加速比没有达到理想线性增长能不能进一步定位是通信瓶颈还是负载不均遇到问题能不能提出解决方案并补测一组数据验证我实验报告里几乎每一项都会补充一段“遇到的问题与优化过程”这也是我和拿到 80 分左右的同学最大的区别。在读研和工作后做高性能计算相关项目我发现课程里的这些分析思维比代码本身值钱得多。真实的生产环境中没人给你一个写好的并行程序更没人告诉你瓶颈在哪你能依靠的就是这套“设计-实现-测试-分析-优化”的完整方法论。所以即便你的目标不是拿高分也建议认真把这个实验的每一个环节做透。本文还有配套的精品资源点击获取