简介:这份郑州大学并行计算实验报告面向计算机与人工智能方向的学习者,尤其适合正在修读并行计算课程、准备实验或希望系统理解多核与分布式编程的本科生与研究生。报告由gyb老师指导完成,作者在该课程中取得90分以上的成绩,内容覆盖任务分解、负载均衡、通信同步等基础理论,共享内存与分布式内存两类并行模型,以及矩阵乘法、FFT、归并排序等并行算法设计。图像绘制与代码分析部分尤为具体,涉及并行渲染、像素操作加速,以及借助Gprof、Intel VTune进行性能瓶颈识别与并行度评估,实验环境涵盖OpenMP、MPI与CUDA等平台。资源以zip压缩包形式提供,整体约37.69MB,包含实验报告文档、绘制图像与代码分析材料,便于对照复现实验流程、理解并行程序优化思路。目前已有699人学习,适合作为课程实验参考与并行编程入门到进阶的实践素材。
1. 并行计算实验报告怎么写才能拿到 90+:从踩坑到复现的完整路径
很多人第一次拿到并行计算实验任务时,第一反应是打开 IDE 写代码,跑通 MPI 的Hello World就以为万事大吉。结果交报告时发现,代码能跑但说不清加速比为什么上不去,画出来的曲线图被批注“数据无意义”,最后成绩卡在 70 分上下。问题不在编程能力,而在于实验报告本身是一个“论证过程”——它需要你证明并行确实带来了收益,并且解释收益为什么没有更高。
这个标题指向的核心场景很明确:某高校并行计算课程,实验环节包含多组代码实现与图像绘制,最终以报告形式提交,目标成绩 90 分以上。适合正在修这门课、需要交实验报告的学生,也适合想系统梳理 MPI/OpenMP 性能分析方法的开发者。接下来我会按“实验环境怎么搭 → 代码怎么写 → 图怎么画 → 坑怎么避 → 报告怎么组织”的顺序,把每个环节的可复现细节讲清楚。
2. 实验环境搭建与第一个 MPI 程序:别让环境问题吃掉两小时
2.1 选 MPI 还是 OpenMP:先看实验要测什么
并行计算实验通常涉及两类场景:多进程消息传递和多线程共享内存。MPI 适合分布式内存模型,进程间通过MPI_Send/MPI_Recv通信,能测出通信开销对加速比的影响;OpenMP 适合单机多核,用#pragma omp parallel就能把循环并行化,适合测线程调度和缓存竞争。
我一般建议:如果实验报告要求画“加速比 vs 进程数”曲线,优先用 MPI,因为进程数可以精确控制,曲线更干净;如果要求分析“数据竞争”或“伪共享”,用 OpenMP 更容易构造对比实验。两者都装也不冲突,但要注意 MPI 和 OpenMP 混编时编译选项要同时带-fopenmp和mpicc。
2.2 在 Linux 上装 MPI 并跑通第一个程序
以下步骤在 Ubuntu 22.04 上验证过,其他发行版包名略有差异。
# 安装 OpenMPI 和编译工具 sudo apt update sudo apt install -y openmpi-bin libopenmpi-dev gcc make # 验证安装 mpirun --version # 应输出类似: mpirun (Open MPI) 4.1.2装完后写第一个 MPI 程序,不要只打印 Hello World,要顺便测一下通信。
// mpi_hello.c #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); // 总进程数 // 每个进程打印自己的 rank,便于确认进程分布 printf("Hello from rank %d of %d\n", rank, size); // 用 Barrier 确保所有进程都到达此处再退出 MPI_Barrier(MPI_COMM_WORLD); MPI_Finalize(); return 0; }编译和运行:
mpicc -O2 -o mpi_hello mpi_hello.c mpirun -np 4 ./mpi_hello参数说明:-O2开启编译优化,避免因未优化导致性能数据失真;-np 4指定 4 个进程,这个数字后面画加速比曲线时会反复改。如果运行时报There are not enough slots available,说明本机核心数不够,加--oversubscribe允许超订,但报告里要注明这是超订场景,不能当作真实加速比。
2.3 用 Makefile 管理编译,避免每次手敲长命令
实验代码会反复改参数、反复编译,手敲命令容易漏选项。写一个简单 Makefile:
CC = mpicc CFLAGS = -O2 -Wall TARGET = mpi_hello all: $(TARGET) $(TARGET): mpi_hello.c $(CC) $(CFLAGS) -o $@ $< clean: rm -f $(TARGET) run: $(TARGET) mpirun -np 4 ./$(TARGET)这样每次改完代码只需make && make run。注意 Makefile 里缩进必须是 Tab,不是空格,否则报missing separator。这个坑几乎每个人都会踩一次。
3. 矩阵乘法并行化:从串行到 MPI 的完整改造与加速比测量
3.1 为什么选矩阵乘法做实验载体
矩阵乘法是并行计算实验的“标准载体”,原因有三:计算量大,并行收益明显;数据划分方式多样,可以对比按行划分、按列划分、分块划分的差异;结果容易验证,串行和并行结果直接比对即可。实验报告里如果能展示三种划分方式的加速比对比,分析深度立刻上一个档次。
假设矩阵规模 N=1024,串行版本用三重循环实现。并行版本我一般用按行划分:把 A 矩阵的 N 行分给 P 个进程,每个进程计算自己负责的那些行与 B 的乘积,最后用MPI_Gather收集结果。
3.2 MPI 矩阵乘法代码与关键参数
// mpi_matmul.c #include <mpi.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define N 1024 // 矩阵规模,实验时可改为 512/1024/2048 对比 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); // 每个进程负责的行数,N 必须能被 size 整除,否则要处理余数 int rows_per_proc = N / size; if (N % size != 0) { if (rank == 0) printf("N must be divisible by process count\n"); MPI_Finalize(); return 1; } // 分配本地矩阵:A_local 为 rows_per_proc x N,B 为 N x N double* A_local = (double*)malloc(rows_per_proc * N * sizeof(double)); double* B = (double*)malloc(N * N * sizeof(double)); double* C_local = (double*)malloc(rows_per_proc * N * sizeof(double)); // 初始化:A_local 每行填 rank+1,B 填 1.0,便于验证结果 for (int i = 0; i < rows_per_proc * N; i++) A_local[i] = rank + 1; for (int i = 0; i < N * N; i++) B[i] = 1.0; // 广播 B 到所有进程,因为每个进程都需要完整的 B MPI_Bcast(B, N * N, MPI_DOUBLE, 0, MPI_COMM_WORLD); // 开始计时,只计计算部分,不计通信初始化 MPI_Barrier(MPI_COMM_WORLD); double start = MPI_Wtime(); // 核心计算:C_local = A_local * B for (int i = 0; i < rows_per_proc; i++) { for (int j = 0; j < N; j++) { double sum = 0.0; for (int k = 0; k < N; k++) { sum += A_local[i * N + k] * B[k * N + j]; } C_local[i * N + j] = sum; } } MPI_Barrier(MPI_COMM_WORLD); double end = MPI_Wtime(); // 收集各进程耗时,取最大值作为并行时间 double local_time = end - start; double max_time; MPI_Reduce(&local_time, &max_time, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD); if (rank == 0) { printf("N=%d, procs=%d, time=%.6f s\n", N, size, max_time); } free(A_local); free(B); free(C_local); MPI_Finalize(); return 0; }逻辑说明:MPI_Bcast把 B 广播给所有进程,这是通信开销的主要来源;计时用MPI_Wtime()配合MPI_Barrier确保所有进程同步后再开始和结束;MPI_Reduce取最大耗时,因为并行总时间由最慢的进程决定。参数方面,N 越大计算占比越高,加速比越接近线性;进程数 size 超过物理核心数后加速比会下降,这个拐点正是报告里要分析的。
3.3 加速比与效率的计算及绘图数据准备
跑完不同进程数后,把数据整理成表。串行时间用mpirun -np 1测,并行时间用-np 2/4/8测。加速比 S = T_serial / T_parallel,效率 E = S / P。
| 进程数 P | 时间 T(s) | 加速比 S | 效率 E |
|---|---|---|---|
| 1 | 12.4 | 1.00 | 1.00 |
| 2 | 6.5 | 1.91 | 0.95 |
| 4 | 3.6 | 3.44 | 0.86 |
| 8 | 2.3 | 5.39 | 0.67 |
这张表直接决定报告里折线图的形状。效率下降的原因要结合通信开销分析:进程数增加后,MPI_Bcast的通信量不变但同步等待变长,计算占比下降,Amdahl 定律开始起作用。
绘图用 Python 的 matplotlib,代码简单但要注意坐标轴标签和单位。
import matplotlib.pyplot as plt procs = [1, 2, 4, 8] speedup = [1.00, 1.91, 3.44, 5.39] efficiency = [1.00, 0.95, 0.86, 0.67] fig, ax1 = plt.subplots() ax1.plot(procs, speedup, 'o-', color='tab:blue', label='Speedup') ax1.set_xlabel('Number of Processes') ax1.set_ylabel('Speedup', color='tab:blue') ax1.set_xticks(procs) ax2 = ax1.twinx() ax2.plot(procs, efficiency, 's--', color='tab:red', label='Efficiency') ax2.set_ylabel('Efficiency', color='tab:red') plt.title('Speedup and Efficiency vs Process Count (N=1024)') fig.tight_layout() plt.savefig('speedup.png', dpi=150)绘图时把 N 和进程数写进标题,方便读者对照。曲线图不要只画一条线,加速比和效率放同一张图用双 Y 轴,信息密度更高,报告里也更容易展开分析。
4. 实验报告里最容易翻车的五个坑:现象、原因与解决
4.1 加速比超过进程数:计时方式错了
现象:4 进程加速比算出 5.2,比理论极限还高。原因:串行时间用了未优化编译的版本,或者计时把初始化也算进去了。解决:串行和并行必须用同一套编译选项(-O2),计时只包裹核心计算段,前后加MPI_Barrier对齐。如果串行时间明显偏大,检查是否误用了-O0。
4.2 结果矩阵全零或部分为零:数据划分没对齐
现象:并行结果和串行对不上,某些行全是 0。原因:rows_per_proc = N / size在 N 不能被 size 整除时丢掉了余数行。解决:要么选 N 为 2 的幂且进程数也是 2 的幂,要么显式处理余数——前N % size个进程多分一行。报告里要写明数据划分策略,这是评分点。
4.3 进程数超过核心数后加速比暴跌:超订不是并行
现象:8 核机器上跑 16 进程,时间反而比 8 进程长。原因:操作系统在物理核心间频繁切换进程,缓存命中率下降。解决:实验进程数不要超过nproc输出的核心数;如果必须超订,报告里要单独说明并解释原因,不能把它当作正常加速比数据画进主曲线。
4.4 绘图数据点太少:曲线没有分析价值
现象:只测了 1、2、4 进程三个点,折线图看不出趋势。原因:实验设计时没规划测量点。解决:至少测 1、2、4、8、16 五个点(受限于核心数可调整),每个点跑 3 次取平均,报告里注明误差范围。数据点多了之后,效率下降的拐点才明显,分析才有依据。
4.5 报告只贴代码不解释参数:阅卷人看不到你的思考
现象:代码占了大半篇幅,但没说为什么选 N=1024、为什么用MPI_Bcast而不是MPI_Scatter。原因:把实验报告当成了代码仓库。解决:每个关键参数后面跟一句选择理由,比如“N=1024 使单进程运行时间在 10 秒左右,既不会太短导致计时误差大,也不会太长导致实验周期不可控”。代码块前后要有逻辑说明,这部分才是拿分的地方。
5. 从 90 分到 95 分:报告组织技巧与验证方法
5.1 报告结构按“问题—方法—证据—分析”四段走
高分报告和及格报告的区别不在代码量,而在论证链条。我一般把每个实验拆成四块:先写“要解决什么问题”(比如验证 MPI 矩阵乘法的加速比上限),再写“用什么方法”(按行划分 + Bcast + Reduce),然后放“证据”(时间表、加速比表、曲线图),最后写“分析”(效率为什么随进程数下降,通信占比多少)。这四块缺一块,分数就掉一档。
验证方法上,除了对比串行结果,还可以做“强扩展性”和“弱扩展性”两组实验。强扩展性固定问题规模 N,增加进程数,看时间是否下降;弱扩展性固定每进程工作量,增加进程数和 N,看时间是否持平。弱扩展性实验能证明你的并行方案没有引入额外开销,是加分项。
5.2 用一张“参数-结果”对照表把实验串起来
报告里最容易被忽略的是参数变化的系统性。建议做一张总表,把 N、进程数、划分方式、通信函数、时间、加速比全部列进去。这样阅卷人一眼就能看出你控制了哪些变量、观察了哪些响应。表格比大段文字更直观,也更容易在答辩时快速定位。
| 实验编号 | N | 进程数 | 划分方式 | 通信函数 | 时间(s) | 加速比 |
|---|---|---|---|---|---|---|
| E1 | 512 | 1 | 按行 | Bcast | 3.1 | 1.00 |
| E2 | 512 | 4 | 按行 | Bcast | 0.9 | 3.44 |
| E3 | 1024 | 4 | 按行 | Bcast | 3.6 | 3.44 |
| E4 | 1024 | 4 | 按列 | Scatter | 4.2 | 2.95 |
这张表里 E3 和 E4 的对比就是分析素材:按列划分需要转置或额外通信,加速比更低。报告里把原因写清楚,比多贴一百行代码都有用。
5.3 图像绘制的三个细节决定专业度
第一,坐标轴必须有单位,时间写“秒”,加速比无量纲但要标注“相对单进程”。第二,图例不要遮挡数据点,用plt.legend(loc='best')让 matplotlib 自动选位置。第三,保存图片用dpi=150以上,插入报告后不模糊。如果实验要求对比多种划分方式,用子图plt.subplot(1,2,1)并排展示,比叠在一起更清晰。
我自己的习惯是每跑完一组数据立刻画图,不要等所有实验做完再补。因为画图时经常发现某个点明显异常,当场重跑比事后猜原因省时间。这个习惯帮我避开了好几次“数据点飞出去”的翻车现场。
5.4 最后检查清单:交报告前过一遍
交之前花十分钟核对:串行和并行编译选项是否一致;计时是否只包裹计算段;进程数是否超过物理核心;数据点是否至少五个;每张图是否有标题、轴标签、单位;每个参数是否有选择理由;结果是否与串行比对过。这七项全过,90 分基本稳了。如果还想冲更高,加一组弱扩展性实验,再把通信开销用MPI_Wtime单独测出来,分析部分就有硬数据支撑。
希望帮到你。
本文还有配套的精品资源,点击获取