
简介这是一份郑州大学计算机与人工智能学院并行计算课程的实验报告由gyb老师指导、学生取得90高分的优质成果适合高校计算机相关专业学生、考研复试备选及对并行计算感兴趣的开发者参考。报告系统梳理了任务分解、负载均衡、共享/分布式内存模型等基础理论并重点呈现了矩阵乘法、FFT、排序算法等典型并行算法的设计思路。实践部分围绕图像绘制与代码分析展开包含并行渲染、像素操作优化以及使用Gprof、Intel VTune等工具进行性能剖析的具体过程同时涵盖OpenMP、MPI、CUDA等主流并行环境的实验配置与经验。压缩包为zip格式约37.69MB适合离线阅读与对照复现。目前已有698人学习下载是理解并行计算理论并提升实战能力的难得资料。 我在郑州大学读研期间修了并行计算这门课实验报告最终拿到90。说句实在话并行计算实验的代码本身并不难网上开源代码一抓一大把真正让分数拉开差距的是报告里那些“看不见的技术含量”——代码分析、绘制图像、性能曲线解读以及踩坑记录。这篇文章我就按自己做实验的完整路径来写从环境搭建、经典实验拆解到如何用Matplotlib把性能数据画成加分图最后再整理几个超高频的报错和排查方法。如果你也在被并行计算实验折磨这篇可以直接当参考模板用。1. 并行计算实验到底在练什么1.1 课程考核与实验报告的结构郑大并行计算这门课实验部分一般会覆盖几类经典问题蒙特卡洛或数值积分求π、矩阵乘法并行化、PSRS排序、PageRank并行迭代等。每个实验要求提交的文档通常包含实验目的、实验环境、算法原理、代码实现、运行结果、性能分析和实验总结。90分和80分的差别往往不在“实验目的”和“算法原理”这些前置部分而在“性能分析”和“运行结果”这两块。老师真正想看的是你能不能把并行程序的性能特征讲清楚而不是仅仅贴一张“运行成功”的截图。比如进程数从1增加到8耗时是线性下降还是趋于平缓加速比是多少效率损失在哪里这些才是报告的核心得分点。1.2 为什么很多人卡在“能跑”这一步并行程序“能跑”太容易了难的是“跑完以后你能说出什么”。我自己见过很多同学程序跑通了代码一贴结果一放就交上去了最后分数基本都卡在85以下。原因很简单报告里缺乏对代码代码分析的拆解也没有用图像把性能趋势可视化出来。这里有个很关键的认知——并行计算实验报告的本质不是“软件开发文档”而是“实验分析小论文”。你要把并行算法当作一个研究对象通过调整进程数、矩阵规模、线程数等参数观察运行时间、加速比、效率的变化再用图表和文字把规律呈现出来。所以绘制图像不是锦上添花而是刚需。2. 实验环境与工具选型2.1 平台选型机房集群 or 本机模拟郑大机房一般提供Linux服务器多节点环境用SSH远程登录后就能编译运行。但如果你懒得跑机房或者实验时间在晚上完全可以在自己的电脑上装多核环境模拟。MPI设计的核心是“多进程协同”本机多核同样能跑mpirun -np 4只是物理核有限性能分析时别太较真数值重点放在趋势上。系统层面我强烈建议用Ubuntu。原因有三一是OpenMPI和gcc一条命令装好省时间二是命令行环境对调试MPI程序更友好三是后续写Python绘图脚本、读CSV数据Linux下不需要处理路径分隔符和编码问题。Windows下用MS-MPI也能跑但环境变量和编译器的坑很多属于自找麻烦。2.2 并行编程框架与编译命令课程实验主要用两种并行模型MPI分布式内存多进程和OpenMP共享内存多线程。前者用于多节点跨机器通信后者用于单机多核并行。重点实验我都用MPI写原因很简单OpenMP的线程并行在代码上太“透明”不好在报告里展开分析通信开销而MPI的Send/Recv过程能清晰体现并行计算的核心矛盾——进程间通信与计算负载均衡之间的博弈。编译运行命令是基本功建议直接记住# 编译MPI程序 mpicc -o pi pi.c -lm # 用4个进程运行 mpirun -np 4 ./pi # 编译OpenMP程序 gcc -fopenmp -o omp_hello omp_hello.c # 设置4个线程运行 OMP_NUM_THREADS4 ./omp_hello这里有个容易忽略的细节mpirun默认只能在本机启动进程如果没有配置hostfile-np超过物理CPU核数时性能数据会非常难看。做实验时尽量让进程数不超过CPU逻辑核数这样测出的加速比曲线才有说服力。2.3 绘图工具链Matplotlib是标准答案绘制图像这块我建议的路线是C/C代码将实验数据写入CSV文件然后用Python的Matplotlib读取并绘图。这样最稳因为C程序擅长算数Python擅长可视化两者各干各的活比在C里调图形库舒服一万倍。Matplotlib的配置也简单核心就几行import pandas as pd import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # 防止中文乱码 plt.rcParams[axes.unicode_minus] False # 解决负号显示异常 df pd.read_csv(result.csv) plt.plot(df[processes], df[time], markero) plt.xlabel(进程数) plt.ylabel(耗时/s) plt.title(不同进程数下并行计算耗时对比) plt.grid(True) plt.savefig(time_curve.png, dpi300, bbox_inchestight)我之前见过不少同学用Excel画图不是不行而是Excel做出来的图在报告里显得不够专业坐标轴标签、数据点标注、图例样式都要手动调效率很低。Matplotlib两行代码就能出出版级插图强烈建议花半小时入门。3. 核心实验代码分析与图像绘制3.1 实验一MPI数值积分求π这个实验是所有并行计算课的第一个“照妖镜”。算法思路很简单用矩形法求定积分把区间[0,1]切分成N份并行分配到各个进程各自算局部和最后归约得到π。核心代码长这样#include mpi.h #include stdio.h #define N 1000000000 int main(int argc, char **argv) { int rank, size, i; double h 1.0 / N; double local_sum 0.0, global_sum 0.0; MPI_Init(argc, argv); MPI_Comm_rank(MPI_COMM_WORLD, rank); MPI_Comm_size(MPI_COMM_WORLD, size); for (i rank; i N; i size) { double x (i 0.5) * h; local_sum 4.0 / (1.0 x * x); } MPI_Reduce(local_sum, global_sum, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); if (rank 0) { printf(pi %.15f\n, global_sum * h); } MPI_Finalize(); return 0; }代码里的核心点有三个第一循环划分方式i size这种strided划分能让每个进程计算的采样点均匀分布避免连续块划分可能带来的缓存局部性问题差异第二MPI_Reduce归约它把所有进程的局部和累加到根进程是聚合通信的典型用法第三浮点累加顺序不固定进程数不同求和顺序就不同所以最后几位数有微小波动属于正常现象这点写进报告非常加分。绘图方面我分别统计了进程数1、2、4、8、16下的运行耗时每个配置跑5次取中位数存成result.csv然后画三张图耗时趋势图、加速比曲线、效率曲线。加速比公式是S T1 / Tp效率是E S / p。从图中可以看出进程少时加速比接近线性进程数到8以上增速明显放缓这就是Amdahl定律在起作用因为程序里串行部分归约和进程启动占比固定并行部分收益递减。3.2 实验二矩阵乘法的并行优化矩阵乘法是并行计算中通信模式最经典的问题。朴素实现的复杂度是O(n^3)并行化核心在于数据划分你可以按行分、按列分也可以做块划分。我报告里用的是行划分的MPI实现每个进程计算一部分结果行。核心思路不复杂根进程把矩阵A的行分组发送给各个worker每个worker收到一整块行数据后与完整的矩阵B做乘法算完后把结果行发回根进程。代码骨架大致如下// 根进程划分并分发矩阵行 if (rank 0) { for (dest 1; dest size; dest) { MPI_Send(matrixA[offset[dest]], count[dest] * n, MPI_DOUBLE, dest, TAG, MPI_COMM_WORLD); } } else { MPI_Recv(localA[0], count[rank] * n, MPI_DOUBLE, 0, TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE); } // 每个进程计算局部矩阵乘法 // 子进程回传结果根进程汇总代码分析部分我写了两层。第一层是计算复杂度每个进程负责约m/p行局部计算量约mnk/p总计算量不变。第二层是通信开销根进程发送矩阵A的各个分块需要(p-1)次Send子进程回发结果也需要(p-1)次Send通信次数随进程数线性增长而每次通信的数据量随矩阵规模增长。当矩阵规模较小、进程数较多时通信开销会超过计算收益性能不升反降这正好引出“临界规模”的概念。绘图这边我画的是“矩阵规模固定n1024时耗时随进程数变化”的折线图以及“进程数固定p4时耗时随矩阵规模变化”的曲线图。后一张图能直观看出并行程序的优势当矩阵规模超过某个阈值后并行计算耗时显著低于串行这个阈值就是通信开销与计算量平衡的转折点。3.3 绘图数据怎么组织与输出很多同学卡在绘图前的数据准备环节。其实C程序里只要在最后把关键数据以CSV格式输出就行比如if (rank 0) { FILE *fp fopen(result.csv, a); fprintf(fp, %d,%f,%f\n, p, elapsed, speedup); fclose(fp); }每次换进程数跑之前记得先删掉旧的CSV文件或者用追加模式写入前加一个判断不然数据会越积越多。我踩过这个坑连续跑了10组数据没清空文件后来画图时曲线乱成一团排查了半天才发现是数据文件混入了重复行。建议写一个独立的run.sh脚本每次自动清理旧文件、循环运行程序并记录数据#!/bin/bash echo processes,time,speedup result.csv for p in 1 2 4 8 16; do mpirun -np $p ./matmul result.csv done这样一条命令跑完全部采集省去手动复制粘贴的麻烦也避免了人为操作失误。绘图脚本读CSV后可以直接画图中间不需要任何人工干预。4. 代码分析怎么写才有水平4.1 不要“贴代码”要“拆代码”一份拿高分的实验报告代码部分不能直接扔一大段源码而应该先写伪码或流程图式的文字描述再贴核心代码片段最后逐层分析。伪码的价值在于剥离语言细节让老师一眼看懂你的并行思路。比如求π实验的伪码可以写成初始化MPI环境获取进程号rank和总进程数size每个进程计算本地的区间步长和循环起点循环计算部分和累加到local_sum调用MPI_Reduce将各进程的local_sum归约到主进程主进程打印最终π值然后是复杂度分析。计算复杂度好理解通信复杂度是很多同学忽略的加分点。以矩阵乘法行划分为例主进程需要发送(p-1)次数据块子进程需要回发(p-1)次结果块所以通信次数为O(p)。每次发送数据量约为n^2/p所以总通信量是O(n^2)。这个结论写进报告再结合实验数据就能解释“为什么进程数越多效率反而下降”的现象。4.2 性能数据要“讲出故事”性能分析的常见误区是只列数据不上结论。比如这样1进程耗时10秒2进程耗时5.2秒4进程耗时3.1秒……然后没了。老师看完完全不知道你理解了没有。正确的写法是先计算加速比和效率再解释数据背后的原因。以我的求π实验数据为例2进程加速比接近1.95效率97%4进程效率降到88%8进程效率只有70%。分析时我会指出进程数翻倍计算量被平分但MPI_Reduce归约所需的通信时间呈对数增长同时多个进程争抢内存带宽访存瓶颈也会拖慢计算。这就是效率下降的两大主因。再补充一句如果矩阵规模足够大计算瓶颈占主导效率下降趋势会明显推迟这就自然引出了“强扩展性”和“弱扩展性”的讨论。老实说“讲出故事”的能力才是90实验报告和普通报告的分水岭。数据只是素材分析才是成品。5. 常见问题与排查技巧5.1 并行程序不加速反而变慢这个问题我遇到太多次了尤其是第一次跑矩阵乘法的时候。排查思路一般是三步先看问题规模再算通信占比最后查代码有没有隐藏的串行瓶颈。如果矩阵只有200x200进程数开到8那程序80%的时间都花在MPI_Send/Recv上当然比串行还慢。解决办法是增大矩阵规模到1024或2048。另外代码里频繁打印也会拖慢速度printf本身就是严重串行操作实验计时区域一定要避开数据输出部分用MPI_Wtime()分别统计通信耗时和计算耗时分段定位瓶颈。5.2 每次运行结果都不一样并行程序多次运行结果不同很大概率不是代码bug而是浮点数累加顺序变了。这是因为MPI_Reduce求和时各进程的到达顺序和归约树结构会随进程数和系统调度变化。遇到这种情况报告中说明原因即可不需要强行修复。如果你的程序还涉及随机数记得固定随机种子否则误差会更大。另外计时也有讲究。第一次运行程序时操作系统要加载可执行文件、建立进程映射耗时偏高。所以每组参数至少跑3次取中位数或最小值不要被第一次运行的假数据干扰。5.3 绘图时中文乱码与坐标轴问题Matplotlib中文乱码是老生常谈了核心就是设置中文字体plt.rcParams[font.sans-serif] [SimHei]如果还乱码说明系统没有SimHei字体需要安装或者换用其他中文字体。另一个常见坑是负号显示成方块要加上plt.rcParams[axes.unicode_minus] False。最后保存图片用dpi300, bbox_inchestight否则插入Word后图片会发虚或者边缘被裁掉。写到这里我再分享一个拿高分的小技巧每次实验报告的末尾我习惯加一个“遇到的问题与解决记录”小节不用刻意总结就是平实地写几段调试过程中的真实记录。这样做的好处是老师能看到你确实在动手做实验、在思考问题而不只是机械地跑通了代码。实验课不是考试过程记录本身就是加分项。这门课最后我拿到的分数是92说实话代码写得不算最漂亮但每个实验的图我都认真画了每个性能现象我都琢磨过为什么最后报告看上去就很“厚实”。这个思路无论你在哪所学校、哪门实验课应该都适用。本文还有配套的精品资源点击获取