304 lines
7.2 KiB
Markdown
304 lines
7.2 KiB
Markdown
# MPI-OpenMP混合并行矩阵乘法实验
|
||
|
||
## 项目结构
|
||
|
||
```
|
||
gemm/
|
||
├── gemm_serial.cpp # 串行版本实现
|
||
├── gemm_parallel.cpp # MPI-OpenMP混合并行版本
|
||
├── xmake.lua # 构建配置文件
|
||
├── run_experiments.sh # 自动化测试脚本
|
||
└── README.md # 本文件
|
||
```
|
||
|
||
## 编译说明
|
||
|
||
### 使用xmake编译(推荐)
|
||
|
||
```bash
|
||
cd /home/yly/dev/hpc-lab-code/work/gemm
|
||
xmake build
|
||
```
|
||
|
||
编译后的可执行文件位于:
|
||
- `build/linux/x86_64/release/gemm_serial`
|
||
- `build/linux/x86_64/release/gemm_parallel`
|
||
|
||
### 手动编译
|
||
|
||
```bash
|
||
# 串行版本
|
||
mpic++ -O3 -march=native gemm_serial.cpp -o gemm_serial
|
||
|
||
# 并行版本
|
||
mpic++ -O3 -march=native -fopenmp gemm_parallel.cpp -o gemm_parallel -lm
|
||
```
|
||
|
||
## 运行说明
|
||
|
||
### 串行版本
|
||
|
||
```bash
|
||
./build/linux/x86_64/release/gemm_serial M N K use-blas
|
||
```
|
||
|
||
参数说明:
|
||
- M: 左矩阵行数
|
||
- N: 左矩阵列数/右矩阵行数
|
||
- K: 右矩阵列数
|
||
- use-blas: 是否使用BLAS(0=不使用,1=使用,当前版本未实现)
|
||
|
||
示例:
|
||
```bash
|
||
./build/linux/x86_64/release/gemm_serial 1024 1024 1024 0
|
||
```
|
||
|
||
### 并行版本
|
||
|
||
```bash
|
||
mpirun -np <进程数> ./build/linux/x86_64/release/gemm_parallel M N K
|
||
```
|
||
|
||
参数说明:
|
||
- 进程数: MPI进程数量
|
||
- M, N, K: 矩阵维度
|
||
|
||
示例:
|
||
```bash
|
||
# 使用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
|
||
```
|
||
|
||
## 自动化测试
|
||
|
||
使用提供的脚本自动运行所有实验并收集数据:
|
||
|
||
```bash
|
||
cd /home/yly/dev/hpc-lab-code/work/gemm
|
||
./run_experiments.sh
|
||
```
|
||
|
||
脚本会自动:
|
||
1. 编译程序
|
||
2. 运行串行基准测试
|
||
3. 运行实验一:固定OpenMP线程数,改变MPI进程数
|
||
4. 运行实验二:同时改变MPI进程数和OpenMP线程数
|
||
5. 运行实验三:固定总处理器数,改变MPI/OpenMP组合
|
||
6. 保存所有结果到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`):
|
||
```csv
|
||
M,N,K,Time_ms
|
||
512,512,512,123.45
|
||
1024,1024,1024,987.65
|
||
...
|
||
```
|
||
|
||
**并行结果** (`experiment_results.csv`):
|
||
```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绘图示例
|
||
|
||
```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()
|
||
```
|
||
|
||
## 性能分析与优化
|
||
|
||
### 预期性能瓶颈
|
||
|
||
1. **通信开销**:MPI通信在大规模并行时可能成为瓶颈
|
||
2. **负载不均衡**:带状分块可能导致某些进程工作量较大
|
||
3. **内存带宽**:矩阵乘法是内存密集型操作
|
||
4. **缓存利用率**:小矩阵可能无法充分利用缓存
|
||
|
||
### 可能的优化方向
|
||
|
||
1. **优化分块策略**:
|
||
- 使用二维块循环分块代替带状分块
|
||
- 考虑缓存友好的分块大小
|
||
|
||
2. **优化通信**:
|
||
- 使用非阻塞通信重叠计算和通信
|
||
- 减少通信次数,增加每次通信的数据量
|
||
|
||
3. **优化计算**:
|
||
- 使用SIMD指令(向量化)
|
||
- 优化循环顺序以提高缓存命中率
|
||
- 考虑使用Strassen算法等快速矩阵乘法
|
||
|
||
4. **混合并行优化**:
|
||
- 找到最优的MPI/OpenMP组合
|
||
- 考虑NUMA架构的亲和性
|
||
|
||
## 实验报告要点
|
||
|
||
1. **实验环境**:
|
||
- 硬件配置(CPU核心数、内存大小)
|
||
- 软件环境(MPI版本、编译器版本)
|
||
|
||
2. **实验结果**:
|
||
- 三个实验的数据表格
|
||
- 性能曲线图
|
||
- 加速比和效率分析
|
||
|
||
3. **结果分析**:
|
||
- 不同并行策略的性能比较
|
||
- MPI进程数和OpenMP线程数的最优组合
|
||
- 矩阵规模对并行效率的影响
|
||
|
||
4. **优化方案**:
|
||
- 识别性能瓶颈
|
||
- 提出优化策略
|
||
- 实施优化并对比效果
|
||
|
||
5. **结论**:
|
||
- MPI-OpenMP混合并行的优势
|
||
- 最佳实践建议
|
||
- 进一步改进方向
|
||
|
||
## 故障排除
|
||
|
||
### 编译错误
|
||
|
||
如果遇到MPI相关错误:
|
||
```bash
|
||
# 检查MPI是否安装
|
||
which mpic++
|
||
mpic++ --version
|
||
|
||
# 检查OpenMP支持
|
||
echo | clang++ -x c++ - -fopenmp -E - > /dev/null
|
||
```
|
||
|
||
### 运行时错误
|
||
|
||
如果遇到MPI运行错误:
|
||
```bash
|
||
# 检查MPI进程数是否合理
|
||
# 确保系统有足够的资源
|
||
|
||
# 检查OpenMP线程数设置
|
||
echo $OMP_NUM_THREADS
|
||
```
|
||
|
||
### 性能异常
|
||
|
||
如果性能不如预期:
|
||
1. 检查CPU频率是否正常(是否降频)
|
||
2. 关闭其他占用资源的程序
|
||
3. 检查系统负载
|
||
4. 确认编译优化选项已启用(-O3)
|
||
|
||
## 参考资料
|
||
|
||
- MPI教程:https://mpitutorial.com/
|
||
- OpenMP官方文档:https://www.openmp.org/
|
||
- 并行编程模式:https://patterns.eecs.berkeley.edu/
|