MPI-OpenMP混合并行矩阵乘法实验
项目结构
gemm/
├── gemm_serial.cpp # 串行版本实现
├── gemm_parallel.cpp # MPI-OpenMP混合并行版本
├── xmake.lua # 构建配置文件
├── run_experiments.sh # 自动化测试脚本
└── README.md # 本文件
编译说明
使用xmake编译(推荐)
cd /home/yly/dev/hpc-lab-code/work/gemm
xmake build
编译后的可执行文件位于:
build/linux/x86_64/release/gemm_serialbuild/linux/x86_64/release/gemm_parallel
手动编译
# 串行版本
mpic++ -O3 -march=native gemm_serial.cpp -o gemm_serial
# 并行版本
mpic++ -O3 -march=native -fopenmp gemm_parallel.cpp -o gemm_parallel -lm
运行说明
串行版本
./build/linux/x86_64/release/gemm_serial M N K use-blas
参数说明:
- M: 左矩阵行数
- N: 左矩阵列数/右矩阵行数
- K: 右矩阵列数
- use-blas: 是否使用BLAS(0=不使用,1=使用,当前版本未实现)
示例:
./build/linux/x86_64/release/gemm_serial 1024 1024 1024 0
并行版本
mpirun -np <进程数> ./build/linux/x86_64/release/gemm_parallel M N K
参数说明:
- 进程数: MPI进程数量
- M, N, K: 矩阵维度
示例:
# 使用4个MPI进程,矩阵大小2048x2048x2048
mpirun -np 4 ./build/linux/x86_64/release/gemm_parallel 2048 2048 2048
# 使用16个MPI进程,8个OpenMP线程
export OMP_NUM_THREADS=8
mpirun -np 16 ./build/linux/x86_64/release/gemm_parallel 4096 4096 4096
自动化测试
使用提供的脚本自动运行所有实验并收集数据:
cd /home/yly/dev/hpc-lab-code/work/gemm
./run_experiments.sh
脚本会自动:
- 编译程序
- 运行串行基准测试
- 运行实验一:固定OpenMP线程数,改变MPI进程数
- 运行实验二:同时改变MPI进程数和OpenMP线程数
- 运行实验三:固定总处理器数,改变MPI/OpenMP组合
- 保存所有结果到CSV文件
实验设计
实验一:MPI进程数扩展性
目的:研究在OpenMP线程数固定为1时,不同MPI进程数的性能表现
变量:
- 固定:OpenMP线程数 = 1
- 改变:MPI进程数 = 1, 2, 4, 9, 16
- 测试:不同矩阵尺寸 512, 1024, 2048, 4096
测量指标:
- 运行时间(ms)
- 加速比 = T_serial / T_parallel
- 效率 = 加速比 / MPI进程数
实验二:MPI-OpenMP混合并行扩展性
目的:研究同时改变MPI进程数和OpenMP线程数时的性能表现
变量:
- OpenMP线程数:1, 2, 4, 8
- MPI进程数:1, 2, 4, 9, 16
- 总处理器数 = MPI进程数 × OpenMP线程数
- 测试:不同矩阵尺寸 512, 1024, 2048, 4096
测量指标:
- 运行时间(ms)
- 加速比 = T_serial / T_parallel
- 效率 = 加速比 / 总处理器数
实验三:MPI/OpenMP组合优化
目的:在总处理器数固定的情况下,研究不同MPI/OpenMP组合对性能的影响
变量:
- 固定:总处理器数 = 16
- 改变:MPI/OpenMP组合
- 1 MPI进程 × 16 OpenMP线程
- 2 MPI进程 × 8 OpenMP线程
- 4 MPI进程 × 4 OpenMP线程
- 8 MPI进程 × 2 OpenMP线程
- 16 MPI进程 × 1 OpenMP线程
- 测试:不同矩阵尺寸 512, 1024, 2048, 4096
测量指标:
- 运行时间(ms)
- 加速比 = T_serial / T_parallel
- 效率 = 加速比 / 总处理器数
数据处理与绘图
输出文件格式
串行结果 (serial_results.csv):
M,N,K,Time_ms
512,512,512,123.45
1024,1024,1024,987.65
...
并行结果 (experiment_results.csv):
Experiment,M,N,K,MPI_Processes,OpenMP_Threads,Time_ms,Speedup,Efficiency
Exp1,512,512,512,1,1,120.34,1.0267,1.0267
Exp1,512,512,512,2,1,65.43,1.8873,0.9437
...
绘图建议
使用Python (matplotlib)、Excel或R进行绘图:
图1:实验一 - MPI进程数扩展性
- X轴:MPI进程数
- Y轴:加速比(左轴)、效率(右轴)
- 不同线条:不同矩阵尺寸
- 预期:加速比随进程数增加,但效率可能下降
图2:实验二 - 总处理器数扩展性
- X轴:总处理器数
- Y轴:加速比(左轴)、效率(右轴)
- 不同线条:不同OpenMP线程数
- 预期:混合并行可能比纯MPI或纯OpenMP更高效
图3:实验三 - MPI/OpenMP组合影响
- X轴:MPI进程数
- Y轴:效率
- 不同线条:不同矩阵尺寸
- 预期:存在最优的MPI/OpenMP组合
Python绘图示例
import pandas as pd
import matplotlib.pyplot as plt
# 读取数据
df = pd.read_csv('experiment_results.csv')
# 实验一:MPI扩展性
exp1 = df[df['Experiment'] == 'Exp1']
fig, ax1 = plt.subplots(figsize=(10, 6))
for size in exp1['M'].unique():
data = exp1[exp1['M'] == size]
ax1.plot(data['MPI_Processes'], data['Speedup'],
marker='o', label=f'{size}x{size}')
ax1.set_xlabel('MPI进程数')
ax1.set_ylabel('加速比')
ax1.set_title('实验一:MPI进程数扩展性(OpenMP=1)')
ax1.legend()
ax1.grid(True)
plt.savefig('exp1_speedup.png')
plt.show()
性能分析与优化
预期性能瓶颈
- 通信开销:MPI通信在大规模并行时可能成为瓶颈
- 负载不均衡:带状分块可能导致某些进程工作量较大
- 内存带宽:矩阵乘法是内存密集型操作
- 缓存利用率:小矩阵可能无法充分利用缓存
可能的优化方向
-
优化分块策略:
- 使用二维块循环分块代替带状分块
- 考虑缓存友好的分块大小
-
优化通信:
- 使用非阻塞通信重叠计算和通信
- 减少通信次数,增加每次通信的数据量
-
优化计算:
- 使用SIMD指令(向量化)
- 优化循环顺序以提高缓存命中率
- 考虑使用Strassen算法等快速矩阵乘法
-
混合并行优化:
- 找到最优的MPI/OpenMP组合
- 考虑NUMA架构的亲和性
实验报告要点
-
实验环境:
- 硬件配置(CPU核心数、内存大小)
- 软件环境(MPI版本、编译器版本)
-
实验结果:
- 三个实验的数据表格
- 性能曲线图
- 加速比和效率分析
-
结果分析:
- 不同并行策略的性能比较
- MPI进程数和OpenMP线程数的最优组合
- 矩阵规模对并行效率的影响
-
优化方案:
- 识别性能瓶颈
- 提出优化策略
- 实施优化并对比效果
-
结论:
- MPI-OpenMP混合并行的优势
- 最佳实践建议
- 进一步改进方向
故障排除
编译错误
如果遇到MPI相关错误:
# 检查MPI是否安装
which mpic++
mpic++ --version
# 检查OpenMP支持
echo | clang++ -x c++ - -fopenmp -E - > /dev/null
运行时错误
如果遇到MPI运行错误:
# 检查MPI进程数是否合理
# 确保系统有足够的资源
# 检查OpenMP线程数设置
echo $OMP_NUM_THREADS
性能异常
如果性能不如预期:
- 检查CPU频率是否正常(是否降频)
- 关闭其他占用资源的程序
- 检查系统负载
- 确认编译优化选项已启用(-O3)
参考资料
- MPI教程:https://mpitutorial.com/
- OpenMP官方文档:https://www.openmp.org/
- 并行编程模式:https://patterns.eecs.berkeley.edu/